Android FFmpeg系列——0 编译.so库
Android FFmpeg系列——1 播放视频
Android FFmpeg系列——2 播放音频
Android FFmpeg系列——3 C多线程使用
Android FFmpeg系列——4 子线程播放音视频
Android FFmpeg系列——5 音视频同步播放
Android FFmpeg系列——6 Java 获取播放进度
Android FFmpeg系列——7 实现快进/快退功能

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

前言

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

环境搭建

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

这里主要说2点:

  • 项目目录结构;
  • 文件配置,主要是app模块build.gradleCMakeLists.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 #include #include #include 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 JNICALLJava_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 播放视频流程如下:

  1. 注册组件
  2. 打开视频文件
  3. 查找视频文件的流信息
  4. 查找视频编码器并打开
  5. 播放视频准备
  6. 视频格式转换准备
  7. 循环读取帧
  8. 解码
  9. 视频格式转换
  10. 播放视频
  11. 释放

代码已经注释得比较清楚了,相信大家看得懂!!

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主要是集成时需要谨慎,还有就是对这个库不太了解!

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

更多相关文章

  1. Android:混合轮播视频和图片
  2. Android中Gallery和ImageSwitcher同步自动(滚动)播放图片库
  3. 功能强大的Vitamio视频播放器的使用教程
  4. android内部培训视频_第四节(1)_异步网络操作
  5. Android内存监听的方法
  6. [Android] 停止、恢复 背影音乐的播放
  7. Android视频录制、播放的两种方式
  8. IJKPlayer 实现视频播放业务
  9. android 音乐播放器中播放模式的设计

随机推荐

  1. android 几个工具方法
  2. 转:android 各种 Adapter
  3. android调用手机铃声
  4. ERROR: Could not find com.android.tool
  5. Android开发20——单个监听器监听多个按
  6. 带有CheckBox的ListView,实现删除和选中功
  7. Android开发者指南(12) —— Android(安
  8. Android RILD学习
  9. 【Android自学笔记】Android获取手机和存
  10. Android实现简单购物车功能