美文网首页日常办公office
FFmpeg入门系列教程(三)

FFmpeg入门系列教程(三)

作者: zjjcc | 来源:发表于2018-03-24 20:43 被阅读1079次

    视频解码,保存为yuv

    1、解码流程

    解码流程图

    2、相关函数

        * av_register_all():注册所有组件

        * avformat_open_input():打开输入视频文件

        * avformat_find_stream_info():获取视频文件信息

        * avcodec_find_decoder():查找解码器

        * avcodec_open():打开解码器

        * av_read_frame():从输入文件中读取一帧压缩数据

        * avcodec_send_packet():解码一帧压缩数据

        *avcodec_receive_frame():接收解码后的数据

        *avcodec_send_frame():发送未编码的数据

        *avcodec_receive_packet():接收编码后的数据

        * avcodec_close():关闭解码器

        * avformat_close_input():关闭输入视频文件

    3、示例代码

        从视频文件解码视频保存为h264、yuv, ffmpeg的版本为3.4.1 链接:地址

    #define __STDC_CONSTANT_MACROS

    #include "stdio.h"

    //引入ffmpeg中的相关头文件

    extern "C" {

    #include "libavcodec\avcodec.h"

    #include "libavformat/avformat.h"

    #include "libswscale/swscale.h"

    }

    int main(int argc, char* argv[])

    {

        AVFormatContext *pFormatCtx = NULL;        //声明了AVFormatContext视频数据指针

        int videoIndex = -1;                        //视频的位置索引

        AVCodecContext *pCodecCtx = NULL;          //解码器信息指针

        AVCodec *pCodec = NULL;                    //解码器指针

        AVFrame *pFrame = NULL;                    //像素数据指针 解码出来的

        AVFrame *pFrameYUV = NULL;                  //经过转码之后的

        uint8_t *out_buffer = NULL;                //缓冲数组

        AVPacket *packet = NULL;                    //h.264包数据指针

        AVCodecParameters *pCodecParam = NULL;      //码流的属性结构体

        int ret = -1;                              //返回值

        struct SwsContext *img_convert_ctx = NULL;  //图像转换格式上下文信息

        int frame_cnt = 0;                        //总的帧数

        const char* filepath = "test.flv";          //输入文件路径

        //注册所有相关组件

        av_register_all();

        //分配空间

        pFormatCtx = avformat_alloc_context();

        //打开文件

        if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0)

        {

            printf("Couldn't open input file.\n");

            return -1;

        }

        //获取视频流信息

        if (avformat_find_stream_info(pFormatCtx, NULL) < 0)

        {

            printf("Couldn't find stream information.\n");

            return -1;

        }

        //打开了视频并且获取了视频流 ,设置视频索引值默认值

        for (int i = 0; inb_streams; i++)

        {

            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)

            {

                videoIndex = i;

                break;

            }

        }

        //如果没有找到视频的索引,说明并不是一个视频文件

        if (videoIndex == -1)

        {

            printf("Couldn't find a video stream.\n");

            return -1;

        }

        //取得视频的解码器信息

        pCodecParam = pFormatCtx->streams[videoIndex]->codecpar;

        //分配空间

        pCodecCtx = avcodec_alloc_context3(NULL);

        //获取编解码器上下文信息

        if (avcodec_parameters_to_context(pCodecCtx, pCodecParam) < 0)

        {

            return -1;

        }

        //得到的解码器

        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

        //解码器未找到

        if (pCodec == NULL)

        {

            printf("Couldn't find codec.\n");

            return -1;

        }

        //打开解码器

        if (avcodec_open2(pCodecCtx, pCodec, NULL)<0)

        {

            printf("Couldn't open codec.\n");

            return -1;

        }

        //初始化yuv容器,并且初始化内存空间

        pFrame = av_frame_alloc();

        pFrameYUV = av_frame_alloc();

        out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));

        //设置图像内容

        avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

        //初始化h.264容器

        packet = (AVPacket *)av_malloc(sizeof(AVPacket));

        av_dump_format(pFormatCtx, 0, filepath, 0);

        //上文说的对图形进行宽度上方的裁剪,以便于显示的更好

        img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,

                                        pCodecCtx->width, pCodecCtx->height,       AV_PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);

        FILE *fp_h264 = NULL;

        fopen_s(&fp_h264, "fp264.h264", "wb+");

        FILE *fp_yuv = NULL;

        fopen_s(&fp_yuv, "fpyuv.yuv", "wb+");

        FILE *fp_yuv_y = NULL;

        fopen_s(&fp_yuv_y, "fp_yuv_y.yuv", "wb+");

        //如果读流成功

        while (av_read_frame(pFormatCtx, packet) >= 0)

        {

            if (packet->stream_index == videoIndex)

            {

                //在此处添加输出H264码流的代码 取自于packet,使用fwrite()

                fwrite(packet->data, 1, packet->size, fp_h264);

                ret = avcodec_send_packet(pCodecCtx, packet);

                if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)

                {

                    av_packet_unref(packet);

                    return -1;

                }

                ret = avcodec_receive_frame(pCodecCtx, pFrame);

                if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)

                {

                    av_packet_unref(packet);

                    return -1;

                }

                else

                {

                    //上文说的对图形进行宽度上方的裁剪,以便于显示的更好

                    sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,

                              pFrameYUV->data, pFrameYUV->linesize);

                    printf("Decoded frame index: %d\n", frame_cnt);

                    //在此处添加输出YUV的代码  取自于pFrameYUV,使用fwrite()

                    printf("CodecCtx->width=%d------pCodecCtx->height%d\n", pCodecCtx->width, pCodecCtx->height);

                    fwrite(pFrameYUV->data[0], 1, (pCodecCtx->width)*(pCodecCtx->height),  fp_yuv_y);

                    fwrite(pFrameYUV->data[0], 1, (pCodecCtx->width)*(pCodecCtx->height),  fp_yuv);

                    fwrite(pFrameYUV->data[1], 1, (pCodecCtx->width)*(pCodecCtx->height)/4, fp_yuv);

                    fwrite(pFrameYUV->data[2], 1, (pCodecCtx->width)*(pCodecCtx->height)/4, fp_yuv);

                    //计数

                    frame_cnt++;

                }

            }

            //释放packet

            av_free_packet(packet);

        }

        fclose(fp_h264);

        fclose(fp_yuv);

        fclose(fp_yuv_y);

        //释放相关资源

        sws_freeContext(img_convert_ctx);

        av_frame_free(&pFrameYUV);

        av_frame_free(&pFrame);

        avcodec_close(pCodecCtx);

        avformat_close_input(&pFormatCtx);

        return 0;

    }

    解码后yuv格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素,以高度Y数据为例,data[0]中一共包含了linesize[0]*height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。因此需要使用ses_scale()进行转换。转换后去除了无效数据,width和linesize[0]就取值相同了。

     avcodec_send_packet():解码一帧压缩数据

     avcodec_receive_frame():接收解码后的数据

     avcodec_send_frame():发送未编码的数据

     avcodec_receive_packet():接收编码后的数据

    在这4个函数中的返回值中,都会有两个错误AVERROR(EAGAIN)和AVERROR_EOF。

    如果是发送函数报AVERROR(EAGAIN)的错,表示已发送的AVPacket还没有被接收,不允许发送新的AVPacket。如果是接

    收函数报这个错,表示没有新的AVPacket可以接收,需要先发送AVPacket才能执行这个函数。

    而如果报AVERROR_EOF的错,在以上4个函数中都表示编解码器处于flushed状态,无法进行发送和接收操作。

    下节博客将实现FFmpeg解码视频中的音频流保存为pcm

    相关文章

      网友评论

        本文标题:FFmpeg入门系列教程(三)

        本文链接:https://www.haomeiwen.com/subject/criqcftx.html