使用FFMPEG封装YUV数据

        未经过编码处理的yuv数据直接存储将占用大量的磁盘空间,而且需要记录像素格式、分辨率等额为信息,一般的播放器也不支持直接打开。因此需要对原始数据进行编码处理,放入特定的文件中,也称为容器。

        典型的多媒体容器格式如FLV、MP4、AVI和RMVB等,容器可以包含不同的类型的音视频流或者字幕流,以及正常播放所需要的同步信息。容器格式在此不是重点,待有需要的时候再做研究。

生成YUV数据

        我们先利用命令行获取一个yuv数据,命令如下所示。该命令可以从src.mp4文件中提取出10秒钟的视频数据,像素格式为YUV420P,分辨率为640*480,帧率为25。

ffmpeg -i src.mp4 -t 10 -pixel_format yuv420p -framerate 25
 -video_size 640*480 640_480_yuv420p.yuv

   封装YUV 数据    

        使用命令行将刚刚得到的yuv数据封装成mp4文件,命令如下

ffmpeg -pixel_format yuv420p -video_size 640*480 -r 25 -i 640_480_yuv420p.yuv
 out.mp4

        使用代码的方式相对命令行要复杂一些,主要的代码如下:

#define PRINT_ERROR(ret)\
    if (ret < 0) {\
        char buf[100]={0};\
        av_strerror(ret,buf,100);\
        qDebug()<<buf;\
        return ;\
    }\

static int check_pixel_fmt(const AVCodec *codec,
                            enum AVPixelFormat pixel_fmt) {
    const enum AVPixelFormat *p = codec->pix_fmts;
    while (*p != AV_PIX_FMT_NONE) {
        if (*p == pixel_fmt) return 1;
        p++;
    }
    return 0;
}

static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c,
                       AVStream *st, AVFrame *frame)
{
    int ret;

    // send the frame to the encoder
    ret = avcodec_send_frame(c, frame);
    if (ret < 0) {
        return -1;
    }

    while (ret >= 0) {
        AVPacket pkt = { 0 };

        ret = avcodec_receive_packet(c, &pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            break;
        else if (ret < 0) {

          return -1;
        }

        /* rescale output packet timestamp values from codec to stream timebase */
        av_packet_rescale_ts(&pkt, c->time_base, st->time_base);
        pkt.stream_index = st->index;

        /* Write the compressed frame to the media file. */

        ret = av_interleaved_write_frame(fmt_ctx, &pkt);
        av_packet_unref(&pkt);
        if (ret < 0) {

           return -1;
        }
    }

    return ret == AVERROR_EOF ? 1 : 0;
}

void muxing()
{
    AVOutputFormat * outputFmt;
    AVFormatContext *formatCtx;
    const char* outFileName = "d:/CODE/useFfmpeg/out2.mp4";
    avformat_alloc_output_context2(&formatCtx,NULL,"mp4",outFileName);
    outputFmt = formatCtx->oformat;

    /*查找编码器,初始化编码器上下文*/
    AVCodecContext* codecCtx;
    AVCodec* codec ;
    AVCodecID codecId = outputFmt->video_codec;
    codec = avcodec_find_encoder(codecId);
    codecCtx = avcodec_alloc_context3(codec);
    codecCtx->codec_id = codecId;
    //codecCtx->bit_rate = 400000;
    codecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    codecCtx->width = 640;
    codecCtx->height = 480;
    codecCtx->time_base = AVRational{1,25};

    /*检查编码器是否支持设置的像素格式*/
    if(check_pixel_fmt(codec,codecCtx->pix_fmt) == 0)
    {
        return;
    }

    int ret = avcodec_open2(codecCtx,codec,nullptr);
    PRINT_ERROR(ret)

    /*创建输出流*/
    AVStream * st;
    st = avformat_new_stream(formatCtx,NULL);
    st->id = formatCtx->nb_streams-1;
    st->time_base =(AVRational){ 1, 25 };
    /*将AVCodecContext中的音视频流参数复制到AVCodecParameter中,如果没有这一步操作,avformat_write_header会失败并返回-22*/
    avcodec_parameters_from_context(st->codecpar,codecCtx);

    /*打开输出文件*/
    ret = avio_open(&formatCtx->pb, outFileName, AVIO_FLAG_WRITE);
    PRINT_ERROR(ret);
    /*写入文件头信息 */
    ret = avformat_write_header(formatCtx, NULL);
    PRINT_ERROR(ret);


    /*创建AVFrame,用于接收从文件读取的YUV数据*/
    AVFrame* frame = av_frame_alloc();
    frame->width = codecCtx->width;
    frame->height = codecCtx->height;
    frame->format = codecCtx->pix_fmt;
    frame->pts = 0;
    ret= av_image_alloc(&frame->data[0],&frame->linesize[0],codecCtx->width,codecCtx->height,codecCtx->pix_fmt,1);
    //ret = av_frame_get_buffer(frame,0);
    PRINT_ERROR(ret);


    QFile inFile("d:/CODE/useFfmpeg/640_480_yuv420p.yuv");
    inFile.open(QIODevice::ReadOnly);
    /*一帧图片所占用的字节数*/
    int imageSize = av_image_get_buffer_size(codecCtx->pix_fmt,codecCtx->width,codecCtx->height,1);

    while (1) {

        ret = inFile.read((char*)frame->data[0],imageSize);
        if(ret <=0)
        {
            break;
        }

        if( write_frame(formatCtx,codecCtx,st,frame) < 0)
        {
            break;
        }

        frame->pts++;
    }
    write_frame(formatCtx,codecCtx,st,NULL);
    av_write_trailer(formatCtx);

    /*释放资源*/
    avio_close(formatCtx->pb);
    av_freep(&frame->data[0]);
    av_frame_free(&frame);
    avcodec_free_context(&codecCtx);
    avformat_free_context(formatCtx);
}

         问题记录

        两种方式输出的mp4文件有些许差异,代码输出的mp4文件帧率为25.11,比特率也比命令行输出的要高。

        我以为是比特率不同导致,于是将命令行和代码输出的文件比特率都设置成一样,但是最终代码输出的视频帧率还是不为整数。

 

        ps:设置输出比特率,命令行需要增加参数 -b:v ,代码中需要设置

AVCodecContext结构体的bit_rate参数。
ffmpeg -pixel_format yuv420p -video_size 640*480 -r 25 -i 640_
480_yuv420p.yuv -b:v 400000 out1.mp4

        关于这个帧率的问题目前还没查找到原因。如果有大佬知道如何修改,请不吝赐教。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值