FFmpeg 之 本地文件解码

之前介绍了如何在ubuntu 系统下编译ffmpeg源码成so动态库,本篇文章记录ffmpeg解码本地文件的使用,和渲染在屏幕上。本篇文章及以后的ffmpeg的操作都是在Android 平台开发实现,ffmpeg 的版本是3.4版。

在贴出代码之前先简单介绍下 用到的几个结构体的一些作用:AVFormatContext、AVCodecContext,AVCodec、AVPacket 、AVFrame。

AVFormatContext :  包含码流参数相关的结构体,其中有媒体流的数量(视频流,音频流),提取相关流中的一些信息,详细的结构体可以查看雷神的AVFormatContext结构体分析

AVCodecContext :  包含了编解码器的一些配置信息,视频流的话有 视频的宽高,图片像素格式,视频的编码格式,MPEG4、h264、hevc(h265)等编码格式,对于音频的话,包含了音频采样率(sample_rate),音频采样格式(sample_fmt),声道数(channels),音频的编码格式:pcm、aac等相关信息,详细信息查看雷神的AVCodecContext结构分析

AVCodec :  存储了编解码器相关信息的结构体,详细信息可以查看雷神的AVCodec 结构体解析

AVPacket : 存储了解码前的数据信息的结构体,详细信息可以查看雷神的AVPacket 结构体解析

AVFrame :存储了解码后的数据信息的结构体,详细信息可以查看雷神的AVFrame结构体解析

再来看看代码中用到的几个重要的ffmpeg的方法:

avformat_open_input : 将需要解码的文件的一些流信息配置到AVFormatContext结构体中,后期主要用avformatcontext这个结构体来查找流信息。

avformat_find_stream_info: 查找AVFormatContext 结构体中的流的详细信息

avcodec_parameters_to_context : 将媒体流中的结构体AVCodecParameters中的媒体信息拷贝到AVCodecContext中,已用于后面的解码操作。

avcodec_find_decoder:根据AVCodecContext中的codec_id 来查找 软解码器,也可以通过avcodec_find_decoder_by_name 来查找硬解码器

avcodec_open2 : 打开解码器,才可以有解码操作

av_read_frame : 在AVFormatContext结构体中读取一包数据 到 AVPacket 中

avcodec_send_packet:将解码前的AVPacket 的数据,发送到FFmpeg解码队列中

avcodec_receive_frame : 获取解码后的一帧数据给到AVFrame结构中

这些操作接可以实现本地文件的解码操作,上代码:

 /*
     * 初始化AVFormatContext 输入流 输出流的上下文,环境
     */
    pFormatCtx = avformat_alloc_context();
    // 打开 视频文件,输入流,并且 读取头部信息到pFormatCtx 当中,
    // 编解码器并没有打开
    if (avformat_open_input(&pFormatCtx,input_str,NULL,NULL) != 0){
        LOGE("Could not open input stream. \n");
        return -1;
    }

    /**
     * 获取文件中的流信息
     * 此函数会读取packet,并确定文件中所有的流信息,
     * 设置pFormatCtx->streams 指向文件中的流
     */
    if (avformat_find_stream_info(pFormatCtx,NULL) != 0){
        LOGE("Could not find stream information. \n");
        return -1;
    }
    videoIndex = -1;
    for (int i = 0; i <pFormatCtx->nb_streams ; ++i) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
            videoIndex = i;
            break;
        }
    }
    if (videoIndex == -1){
        LOGE("Could not find a video stream");
        return -1;
    }

    /**
     * 初始化 pCodeoCtx
     */
    pCodecCtx = avcodec_alloc_context3(NULL);

    /**
     *   将AVCodecParameters 中的音视频信息拷贝到AVCodecContext 中
     */
    avcodec_parameters_to_context( pCodecCtx,pFormatCtx->streams[videoIndex]->codecpar);

    // 根据 文件中的解码器id 来查找解码器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if (pCodec == NULL){
        // 找不到解码器
        LOGE("Could not find Codec .\n");
        return  -1;
    }

    /**
     * 打开解码器
     */
    if (avcodec_open2(pCodecCtx,pCodec,NULL) != 0){
        LOGE("Could not open Codec. \n");
        return  -1;
    }

    // 给pFrame 分配内存空间
    pFrame = av_frame_alloc();
    out_buffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height,1));
    av_image_fill_arrays(pFrame->data,pFrame->linesize,out_buffer,
            AV_PIX_FMT_YUV420P,pCodecCtx->width,pCodecCtx->height,1);
    packet = (AVPacket *)av_malloc(sizeof(AVPacket));

    img_convert_ctc = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
            pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);

    frame_cnt = 0;
    time_start = clock();
    /**
     * 读取具体的音视频帧数据
     */
    while (av_read_frame(pFormatCtx,packet)){
        //  判断一包数据的流索引是不是 视频
        if (packet->stream_index == videoIndex){
            //  发送数据到ffmpeg解码队列中
            ret = avcodec_send_packet(pCodecCtx,packet);
            av_packet_unref(packet);
            if (ret < 0){
                LOGE("Send packet Error .\n");
                return  -1;
            }

            // 从解码后的队列中取出一个frame

            ret = avcodec_receive_frame(pCodecCtx,pFrame);
            if (ret < 0){
                LOGE("Decodec Error");
            }
        }

上面只是本地解码的操作,为了做渲染,用到了ANativeWindow 这个结构体,这个是Android NDK中的一个结构体,需要我们引入相关的头文件,这里说下解码,到渲染的操作,解码成功后,将数据放到队列里面,而渲染在另一个线程,这里需要注意的是native渲染支持的像素格式有限,需要对解码后的数据做格式转换,这里用到了SwsContext 这个结构体,这个结构体包含了转换前的数据的宽高,像素格式,和转换后的宽高,像素格式,然后用sws_scale,这个函数进行转操作,最后就是渲染操作了,直接上代码了 ,配置AnativeWindow


    //  声明初始化 ANativeWindow 
    ANativeWindow * nativeWindow = ANativeWindow_fromSurface(env,surface);
    if (nativeWindow == NULL){
        LOGE("Player Error: Can not create native window");
        return;
    }
    render.native_window = nativeWindow;
    
    //  配置nativeWindow 渲染缓存 渲染像素格式
//    result = ANativeWindow_setBuffersGeometry(nativeWindow,video_width,video_height,WINDOW_FORMAT_RGB_565);
    result = ANativeWindow_setBuffersGeometry(nativeWindow,video_width,video_height,WINDOW_FORMAT_RGBA_8888);
    if (result < 0){
        LOGE("Player Error: Can not set native window buffer");
        ANativeWindow_release(nativeWindow);
        return;
    }

RGBA_8888的渲染操作:

void * Rendering(void *render){
    VideoRender *_render = (VideoRender*)render;
    if (_render == NULL){
        LOGE("Player Error: _render is NULL");
    }
    int result;
    while (isRunning){
        if (_render->queue_frame.empty()){
            continue;
        }
        AVFrame  frame = _render->queue_frame.front();
        _render->queue_frame.pop();

        // 数据格式转换 
        result = sws_scale(_render->data_convert_ctx,(const uint8_t * const *)frame.data,frame.linesize,0,
                _render->video_height,
                _render->rgba_frame->data,_render->rgba_frame->linesize);
        if (result < 0){
            LOGE("Player Error: data convert fail");
            return &result;
        }

        result = ANativeWindow_lock(_render->native_window,&(_render->window_buffer),NULL);
        if(result < 0){
            LOGE("Player Error: con not lock native window ");
        } else{
            /**
             *  将图像 绘制到 界面上
             */
            unsigned char * bits = (uint8_t *) _render->window_buffer.bits;
            for (int h = 0; h < _render->video_height ; h++) {
                memcpy(bits + h * _render->window_buffer.stride * 4,
                        _render->out_buffer + h * _render->rgba_frame->linesize[0],
                        _render->rgba_frame->linesize[0]);
            }
            ANativeWindow_unlockAndPost(_render->native_window);
        }

    }
}

这里补一个渲染RGB_565的代码:

 LOGE("Window Render  : window buffer  width %d  height %d",windowBuffer.width,windowBuffer.height);
    if (native_window != NULL ) {
        result = ANativeWindow_lock(native_window, &windowBuffer, NULL);
        if (result < 0) {
            ANativeWindow_release(native_window);
            LOGE("Render Error : Could not lock native window");
        } else {
            if (!isWindowRender){
                callJavaWindowRending();
            }

            uint8_t * bits = (uint8_t *)windowBuffer.bits;
            for (int h = 0; h < video_heigth; h++) {
                memcpy( bits + h * windowBuffer.stride * 2,
                       out_buffer + h * rgab_frame->linesize[0],
                        (size_t)rgab_frame->linesize[0]);
            }
//                    memcpy(bits, rgab_frame->data[0], 4 * video_heigth * video_width);


            if(0 !=  ANativeWindow_unlockAndPost(native_window)){
                LOGE("Window Error : unlocck and post error ");
                return;
            };
        }

这里渲染主要是根据 显示格式占用的字节数来确定 宽高乘积的倍数。

参考博客:

https://blog.csdn.net/leixiaohua1020/article/details/14214577

https://blog.csdn.net/leixiaohua1020/article/details/14215833

​​​​​​​https://blog.csdn.net/leixiaohua1020/article/details/14214859
 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值