Android FFmpeg系列——0 编译.so库
<https://blog.csdn.net/JohanMan/article/details/81565834>
Android FFmpeg系列——1 播放视频
<https://blog.csdn.net/JohanMan/article/details/83091706>
Android FFmpeg系列——2 播放音频
<https://blog.csdn.net/JohanMan/article/details/83107270>
Android FFmpeg系列——3 C多线程使用
<https://blog.csdn.net/JohanMan/article/details/83109147>
Android FFmpeg系列——4 子线程播放音视频
<https://blog.csdn.net/JohanMan/article/details/83416664>
Android FFmpeg系列——5 音视频同步播放
<https://blog.csdn.net/JohanMan/article/details/83176144>
Android FFmpeg系列——6 Java 获取播放进度
<https://blog.csdn.net/JohanMan/article/details/83583443>
Android FFmpeg系列——7 实现快进/快退功能
<https://blog.csdn.net/JohanMan/article/details/83586383>

由于公司项目原因,现在才得空来学习关于FFmpeg库的使用。

<>前言

在使用FFmpeg库的过程中,哎呦,各种心酸!!项目重新创建了N次,调试了N次,终于把视频流播放出来,心里甚是激动呀!

<>环境搭建

Android Studio 创建Demo项目,记得把 “Include c++ support” 勾上。

这里主要说2点:

* 项目目录结构;
* 文件配置,主要是app模块build.gradle 和 CMakeLists.txt 的配置;
<>项目目录结构

每个人放so位置不一样,这个会影响到配置文件,所以还是放出来会好一点。



<>app模块build.gradle
... android { ... defaultConfig { ... externalNativeBuild { cmake { cppFlags
"-std=c++11 -frtti -fexceptions" } ndk { abiFilters "armeabi" } } } ...
externalNativeBuild { cmake { path "CMakeLists.txt" } } } ...
<>CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1) # 自己库文件 add_library( player SHARED
src/main/cpp/player.cpp ) # FFmpeg include 文件
include_directories(src/main/cpp/include) # 编解码库 add_library( avcodec-lib
SHARED IMPORTED ) set_target_properties( avcodec-lib PROPERTIES
IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavcodec.so )
# 滤镜库 暂时没用上 add_library( avfilter-lib SHARED IMPORTED ) set_target_properties(
avfilter-lib PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavfilter.so ) # 文件格式库
大部分操作都需要这个库 add_library( avformat-lib SHARED IMPORTED ) set_target_properties(
avformat-lib PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavformat.so ) # 工具库
add_library( avutil-lib SHARED IMPORTED ) set_target_properties( avutil-lib
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libavutil.so ) # 重采样库 主要用于音频的转换
add_library( swresample-lib SHARED IMPORTED ) set_target_properties(
swresample-lib PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswresample.so ) # 视频格式转换库
主要用于视频的转换 add_library( swscale-lib SHARED IMPORTED ) set_target_properties(
swscale-lib PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi/libswscale.so ) # 主要 android 库
native window 需要这个库 target_link_libraries( player log android avcodec-lib
avfilter-lib avformat-lib avutil-lib swresample-lib swscale-lib )
<>C 代码
#include <jni.h> #include <android/native_window.h> #include
<android/native_window_jni.h> #include <android/log.h> extern "C" { #include
"libavformat/avformat.h" #include "libavcodec/avcodec.h" #include
"libswscale/swscale.h" #include "libavutil/imgutils.h" } // Android 打印 Log #
define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, "player",
FORMAT, ##__VA_ARGS__); /** * 播放视频流 * R# 代表申请内存 需要释放或关闭 */ extern "C" JNIEXPORT
void JNICALL Java_com_johan_player_Player_playVideo(JNIEnv *env, jobject
instance, jstring path_, jobject surface) { // 记录结果 int result; // R1 Java
String -> C String const char *path = env->GetStringUTFChars(path_, 0); // 注册
FFmpeg 组件 av_register_all(); // R2 初始化 AVFormatContext 上下文 AVFormatContext *
format_context= avformat_alloc_context(); // 打开视频文件 result = avformat_open_input
(&format_context, path, NULL, NULL); if (result < 0) { LOGE("Player Error : Can
not open video file"); return; } // 查找视频文件的流信息 result =
avformat_find_stream_info(format_context, NULL); if (result < 0) { LOGE("Player
Error : Can not find video file stream info"); return; } // 查找视频编码器 int
video_stream_index= -1; for (int i = 0; i < format_context->nb_streams; i++) {
// 匹配视频流 if (format_context->streams[i]->codecpar->codec_type ==
AVMEDIA_TYPE_VIDEO) { video_stream_index = i; } } // 没找到视频流 if (
video_stream_index== -1) { LOGE("Player Error : Can not find video stream");
return; } // 初始化视频编码器上下文 AVCodecContext *video_codec_context =
avcodec_alloc_context3(NULL); avcodec_parameters_to_context(video_codec_context,
format_context->streams[video_stream_index]->codecpar); // 初始化视频编码器 AVCodec *
video_codec= avcodec_find_decoder(video_codec_context->codec_id); if (
video_codec== NULL) { LOGE("Player Error : Can not find video codec"); return; }
// R3 打开视频解码器 result = avcodec_open2(video_codec_context, video_codec, NULL); if
(result < 0) { LOGE("Player Error : Can not find video stream"); return; } //
获取视频的宽高 int videoWidth = video_codec_context->width; int videoHeight =
video_codec_context->height; // R4 初始化 Native Window 用于播放视频 ANativeWindow *
native_window= ANativeWindow_fromSurface(env, surface); if (native_window ==
NULL) { LOGE("Player Error : Can not create native window"); return; } //
通过设置宽高限制缓冲区中的像素数量,而非屏幕的物理显示尺寸。 // 如果缓冲区与物理屏幕的显示尺寸不相符,则实际显示可能会是拉伸,或者被压缩的图像 result
= ANativeWindow_setBuffersGeometry(native_window, videoWidth, videoHeight,
WINDOW_FORMAT_RGBA_8888); if (result < 0){ LOGE("Player Error : Can not set
native window buffer"); ANativeWindow_release(native_window); return; } //
定义绘图缓冲区 ANativeWindow_Buffer window_buffer; // 声明数据容器 有3个 // R5 解码前数据容器 Packet
编码数据 AVPacket *packet = av_packet_alloc(); // R6 解码后数据容器 Frame 像素数据 不能直接播放像素数据
还要转换 AVFrame *frame = av_frame_alloc(); // R7 转换后数据容器 这里面的数据可以用于播放 AVFrame *
rgba_frame= av_frame_alloc(); // 数据格式转换准备 // 输出 Buffer int buffer_size =
av_image_get_buffer_size(AV_PIX_FMT_RGBA, videoWidth, videoHeight, 1); // R8 申请
Buffer 内存 uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size * sizeof(
uint8_t)); av_image_fill_arrays(rgba_frame->data, rgba_frame->linesize,
out_buffer, AV_PIX_FMT_RGBA, videoWidth, videoHeight, 1); // R9 数据格式转换上下文 struct
SwsContext*data_convert_context = sws_getContext( videoWidth, videoHeight,
video_codec_context->pix_fmt, videoWidth, videoHeight, AV_PIX_FMT_RGBA,
SWS_BICUBIC, NULL, NULL, NULL); // 开始读取帧 while (av_read_frame(format_context,
packet) >= 0) { // 匹配视频流 if (packet->stream_index == video_stream_index) { // 解码
result= avcodec_send_packet(video_codec_context, packet); if (result < 0 &&
result!= AVERROR(EAGAIN) && result != AVERROR_EOF) { LOGE("Player Error : codec
step 1 fail"); return; } result = avcodec_receive_frame(video_codec_context,
frame); if (result < 0 && result != AVERROR_EOF) { LOGE("Player Error : codec
step 2 fail"); return; } // 数据格式转换 result = sws_scale( data_convert_context, (
const uint8_t* const*) frame->data, frame->linesize, 0, videoHeight, rgba_frame
->data, rgba_frame->linesize); if (result <= 0) { LOGE("Player Error : data
convert fail"); return; } // 播放 result = ANativeWindow_lock(native_window, &
window_buffer, NULL); if (result < 0) { LOGE("Player Error : Can not lock
native window"); } else { // 将图像绘制到界面上 // 注意 : 这里 rgba_frame 一行的像素和
window_buffer 一行的像素长度可能不一致 // 需要转换好 否则可能花屏 uint8_t *bits = (uint8_t *)
window_buffer.bits; for (int h = 0; h < videoHeight; h++) { memcpy(bits + h *
window_buffer.stride * 4, out_buffer + h * rgba_frame->linesize[0], rgba_frame->
linesize[0]); } ANativeWindow_unlockAndPost(native_window); } } // 释放 packet 引用
av_packet_unref(packet); } // 释放 R9 sws_freeContext(data_convert_context); //
释放 R8 av_free(out_buffer); // 释放 R7 av_frame_free(&rgba_frame); // 释放 R6
av_frame_free(&frame); // 释放 R5 av_packet_free(&packet); // 释放 R4
ANativeWindow_release(native_window); // 关闭 R3 avcodec_close(video_codec_context
); // 释放 R2 avformat_close_input(&format_context); // 释放 R1 env->
ReleaseStringUTFChars(path_, path); }
使用 FFmpeg 播放视频流程如下:

* 注册组件
* 打开视频文件
* 查找视频文件的流信息
* 查找视频编码器并打开
* 播放视频准备
* 视频格式转换准备
* 循环读取帧
* 解码
* 视频格式转换
* 播放视频
* 释放
代码已经注释得比较清楚了,相信大家看得懂!!

<>Java 代码

布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="
http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="match_parent"> <
SurfaceView android:id="@+id/surface_view" android:layout_width="match_parent"
android:layout_height="250dp" /> <Button android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="Play" android:onClick="play"
/> </LinearLayout>
Player Native 代码:
public class Player { static { System.loadLibrary("player"); } public native
void playVideo(String path, Surface surface); }
Activity 播放代码:
public class MainActivity extends AppCompatActivity { private SurfaceView
surfaceView; private SurfaceHolder surfaceHolder; @Override protected void
onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); surfaceView = (SurfaceView) findViewById
(R.id.surface_view); surfaceHolder = surfaceView.getHolder(); } public void play
(View view) { String videoPath = Environment.getExternalStorageDirectory() +
"/mv.mp4"; Player player = new Player(); player.playVideo(videoPath,
surfaceHolder.getSurface()); } }
<>效果图



<>小结

使用FFmpeg主要是集成时需要谨慎,还有就是对这个库不太了解!

接下来继续摸索!加油!!