Android_使用FFmpeg播放视频

263 阅读3分钟

前言

对FFmpeg就不做过多介绍了,之前有篇文章是对Linux环境下编译FFmpeg库,有兴趣的也可以看下,Shell脚本编写与执行编译ffmpeg库,编译好之后的继承就不赘述了,有很多博客写的很详细,提一个注意的地方就是gradle版本可能会影响集成是否成功(一直提示couldnt find libnative-lib.so的话建议修改下gradle版本)。 注: 不同版本的FFmpeg差异还是比较大的,本文以4.0.5为例

流程

使用FFmpeg播放视频主要是对视频的解封装,解码,转换,绘制的一个过程。贴一下大致的流程图(牵扯一些视频封装的基本知识,需要的铜须自行百度吧,还是比较简单的~)

上面的流程图大概表述了整个过程,下面逐步实现:

  • 在对视频解码之前需要打开视频文件吧,也就是解封装,这里需要拿到一个比较重要的上下文AVFormatContext,
//拿到AVFormatContext上下文,用于获取文件中的视频流、音频流
AVFormatContext *avFormatContext = avformat_alloc_context();
//打开视频文件,第一个参数是上下文,第二个参数是路径支持本地路径和url地址,
// 第三个参数表示输入参数可以填空(表示自动,视频默认),第四个参数是一个字典,相当于一个HashMap,可以存放很多配置参数,比如超时时间等
int resultCode = avformat_open_input(&avFormatContext, path, NULL, &options);
 //解析文件中的流,一般第一个流是视频流,也有可能是视频+字幕流
int code = avformat_find_stream_info(avFormatContext, NULL);
  • 遍历流获取视频流
    //遍历流
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        //流的类型为视频流,codecpar为解码器
        if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }
  • 拿到视频流之后就是对视频流进行解码、转换和绘制。如果要进行解码,那么要有解码器。解码器需要开启
    //获取解码器
    AVCodec *avCodec = avcodec_find_decoder(codecpar->codec_id);
    //解码器的上下文
    AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodec);
    //将解码器参数copy到解码器上下文
    avcodec_parameters_to_context(avCodecContext, codecpar);
    //打开解码器
    if(avcodec_open2(avCodecContext, avCodec, NULL)<0){
        LOGE("打开解码器失败")
    }
  • 接着申请AVPacket和AVFrame,其中AVPacket的作用是:保存解码之前的数据和一些附加信息,如显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等;AVFrame的作用是:存放解码过后的数据。
    //解码 获取yuv数据,FFmpeg中yuv数据是封装在AVPacket中的
    //申请AVPacket
    AVPacket *avPacket = (AVPacket *) av_malloc(sizeof(AVPacket));   //申请AVPacket
    av_init_packet(avPacket);
    //申请AVFrame
    AVFrame *avFrame = av_frame_alloc();//分配一个AVFrame结构体,AVFrame结构体一般用于存储原始数据,指向解码后的原始帧
    AVFrame *rgb_frame = av_frame_alloc();//分配一个AVFrame结构体,指向存放转换成rgb后的帧
  • 设置rgb_frame是一个缓存区域。
    //缓存区
    uint8_t  *out_buffer= (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA,
                                                                  avCodecContext->width,avCodecContext->height));
    //与缓存区相关联,设置rgb_frame缓存区
    avpicture_fill((AVPicture *)rgb_frame,out_buffer,AV_PIX_FMT_RGBA,avCodecContext->width,avCodecContext->height);
  • 在Native层绘制必须用到ANativeWindow,所以需要获取ANativeWindow。
    //取到nativewindow
    ANativeWindow *aNativeWindow=ANativeWindow_fromSurface(env,surface);
  • 下面就是解码过程和绘制过程
  • A解码
while (av_read_frame(avFormatContext, packet) >= 0) {
        LOGE("解码 %d",avPacket->stream_index)
        LOGE("VideoIndex %d",video_stream_index)
        if(packet->stream_index==video_index){
              LOGE("视频流解码")
            avcodec_decode_video2(avCodecContext, frame, &frameCount, packet)
        }
        av_free_packet(packet);
    }
  • B绘制
if (frameCount) {
                //设置底层窗体buffer缓冲区的大小
                ANativeWindow_setBuffersGeometry(aNativeWindow,avCodecContext->width,avCodecContext->height,WINDOW_FORMAT_RGBA_8888);
                //锁,防止其它线程操作
                ANativeWindow_lock(aNativeWindow, &outBuffer, NULL);
                //yuv数据转换成rgb数据
                //转换为rgb格式
                sws_scale(swsContext,(const uint8_t *const *)avFrame->data,avFrame->linesize,0,
                          avFrame->height,rgb_frame->data,
                          rgb_frame->linesize);
                //  rgb_frame是有画面数据
                uint8_t *dst= (uint8_t *) outBuffer.bits;
                //一行数据  拿到一行有多少个字节 RGBA
                int destStride=outBuffer.stride*4;
                //像素数据的首地址
                uint8_t * src=  rgb_frame->data[0];
                //  实际内存一行数量
                int srcStride = rgb_frame->linesize[0];
                for (int i = 0; i < avCodecContext->height; ++i) {
                    //将rgb_frame中每一行的数据复制给nativewindow
                    memcpy(dst + i * destStride,  src + i * srcStride, srcStride);
                }
                //解锁
                ANativeWindow_unlockAndPost(aNativeWindow);
                usleep(1000 * 16);
            }
        }

最后就是资源回收和Java层的逻辑了,这里就不贴了,感兴趣的看下完整代码吧~ 完整代码见:github