【FFmpeg】YUV数据编码成H264

YUV是视频裸数据流,没有被压缩的,占用内存非常大,不适合在网络上传输,因此咱们平时网络上传输的都是经过压缩的视频流,如咱们平时看的直播都是经过编码压缩的视频流,下载到本地之后进行解码再进行渲染。今天我们就来看一下YUV数据是如何编码成H264数据流的。YUV是裸数据流,所以咱们要实现编码,就必须要知道YUV数据的格式pix_fmt、视频宽高。

一 使用命令行进行编码

ffmpeg -f yuv420p -s 540x960 -i bb1_yuv420p_540x960.yuv output.h264

-f 指定yuv数据格式为yuv420p
-s 指定视频大小为540x960

二 使用代码进行编码

1、通过avformat_alloc_output_context2函数初始化输出文件上下文

avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, h264Path.UTF8String);

2、通过avcodec_find_encoder函数找到编码器

dec = avcodec_find_encoder(AV_CODEC_ID_H264)

3、通过avcodec_alloc_context3初始化编码器上下文

dec_ctx = avcodec_alloc_context3(dec);

4、设置编码器上下文的参数,包括码率、时间基、视频宽高、像素等参数

dec_ctx->width = yuvW;
dec_ctx->height = yuvH;
dec_ctx->framerate = av_make_q(25, 1);
dec_ctx->time_base = av_make_q(1, 25);
dec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
dec_ctx->gop_size = 10;
dec_ctx->bit_rate = 800000;
dec_ctx->max_b_frames = 1;
if (dec_ctx->codec_id == AV_CODEC_ID_H264) {
    av_opt_set(dec_ctx->priv_data, "preset", "slow", 0);
}

5、通过avformat_new_stream函数新建一个视频流,并通过avcodec_parameters_from_context函数把编码器的参数拷贝给视频流

AVStream *st = avformat_new_stream(ofmt_ctx, dec);
ret = avcodec_parameters_from_context(st->codecpar, dec_ctx);

6、通过avcodec_open2函数打开编码器

avcodec_open2(dec_ctx, dec, NULL);

7、通过avio_open函数打开输出文件

avio_open(&ofmt_ctx->pb, h264Path.UTF8String, AVIO_FLAG_WRITE);

8、通过avformat_write_header写文件头

avformat_write_header(ofmt_ctx, NULL);

9、循环从yuv文件中获取视频帧,经过编码后,通过av_interleaved_write_frame函数写入文件

    while (feof(yuv_f)==0) {
        size_t size = fread(yuv_buffer, 1, yuvW*yuvH*3/2, yuv_f);
        if (size<yuvW*yuvH*3/2) {
            printf("fread fail \n");
            break;
        }
        for (int i=0; i<yuvH; i++) {
            memcpy(frame->data[0] + i*frame->linesize[0], yuv_buffer + yuvW*i, yuvW);
        }
        for (int i=0; i<yuvH/2; i++) {
            memcpy(frame->data[1] + (i*frame->linesize[1]), yuv_buffer+yuvH*yuvW + yuvW/2*i, yuvW/2);
        }
        for (int i=0; i<yuvH/2; i++) {
            memcpy(frame->data[2] + (i*frame->linesize[2]), yuv_buffer+yuvH*yuvW*5/4 + yuvW/2*i, yuvW/2);
        }
        frame->pts = pts_i++;
        ret = avcodec_send_frame(dec_ctx, frame);
        if (ret<0) {
            printf("avcodec_send_frame fail \n");
            break;
        }
        while (1) {
            ret = avcodec_receive_packet(dec_ctx, pkt);
            if (ret==AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret<0) {
                printf("avcodec_receive_packet fail \n");
                break;
            }
            ret = av_interleaved_write_frame(ofmt_ctx, pkt);
            if (ret<0) {
                printf("av_interleaved_write_frame fail \n");
                break;
            }
            av_packet_unref(pkt);
        }
    }
    ret = avcodec_send_frame(dec_ctx, NULL);
    if (ret<0) {
        printf("avcodec_send_frame fail \n");
        goto __FAIL;
    }
    while (1) {
        ret = avcodec_receive_packet(dec_ctx, pkt);
        if (ret==AVERROR(EINVAL) || ret == AVERROR_EOF) {
            break;
        } else if (ret<0) {
            printf("avcodec_receive_packet fail \n");
            break;
        }
        ret = av_interleaved_write_frame(ofmt_ctx, pkt);
        if (ret<0) {
            printf("av_interleaved_write_frame fail \n");
            break;
        }
        av_packet_unref(pkt);
    }

这里需要注意的是后面还有一个while循环,这是因为视频帧有I、B、P帧的区别,这就导致可能还有数据在缓存中没有编码完,因此使用一个while循环去读出来。
10、通过av_write_trailer写入文件尾

av_write_trailer(ofmt_ctx);

完整代码如下:

+ (void)convert
{
    NSString *yuvPath = [[NSBundle mainBundle] pathForResource:@"bb1_yuv420p_540x960.yuv" ofType:nil];
    NSString *h264Path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"bb1.h264"];
    NSLog(@"%@", h264Path);
    int yuvW = 540;
    int yuvH = 960;
    int ret;
    AVFormatContext *ofmt_ctx = NULL;
    AVCodecContext *dec_ctx = NULL;
    AVCodec *dec = NULL;
    AVPacket *pkt = NULL;
    AVFrame *frame = NULL;
    FILE *yuv_f = fopen(yuvPath.UTF8String, "rb+");
    ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, h264Path.UTF8String);
    if (ret<0) {
        printf("avformat_alloc_output_context2 fail \n");
        goto __FAIL;
    }
    dec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!dec) {
        printf("avcodec_find_encoder fail \n");
        goto __FAIL;
    }
    dec_ctx = avcodec_alloc_context3(dec);
    dec_ctx->width = yuvW;
    dec_ctx->height = yuvH;
    dec_ctx->framerate = av_make_q(25, 1);
    dec_ctx->time_base = av_make_q(1, 25);
    dec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    dec_ctx->gop_size = 10;
    dec_ctx->bit_rate = 800000;
    dec_ctx->max_b_frames = 1;
    if (dec_ctx->codec_id == AV_CODEC_ID_H264) {
        av_opt_set(dec_ctx->priv_data, "preset", "slow", 0);
    }

    ret = avio_open(&ofmt_ctx->pb, h264Path.UTF8String, AVIO_FLAG_WRITE);
    if (ret<0) {
        printf("avio_open fail \n");
        goto __FAIL;
    }
    ret = avcodec_open2(dec_ctx, dec, NULL);
    if (ret<0) {
        printf("avcodec_open2 fail \n");
        goto __FAIL;
    }
    AVStream *st = avformat_new_stream(ofmt_ctx, dec);
    ret = avcodec_parameters_from_context(st->codecpar, dec_ctx);
    if (ret<0) {
        printf("avcodec_parameters_from_context fail \n");
        goto __FAIL;
    }
    
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret<0) {
        printf("avformat_write_header fail \n");
        goto __FAIL;
    }
    
    uint8_t *yuv_buffer = av_malloc(yuvW*yuvH*3/2);
    frame = av_frame_alloc();
    if (!frame) {
        printf("av_frame_alloc fail \n");
        goto __FAIL;
    }
    frame->width = dec_ctx->width;
    frame->height = dec_ctx->height;
    frame->format = dec_ctx->pix_fmt;
    ret = av_frame_get_buffer(frame, 0);
    if (ret<0) {
        printf("av_frame_get_buffer fail \n");
        goto __FAIL;
    }
    pkt = av_packet_alloc();
    if (!pkt) {
        printf("av_packet_alloc fail \n");
        goto __FAIL;
    }
    int pts_i = 0;
    while (feof(yuv_f)==0) {
        size_t size = fread(yuv_buffer, 1, yuvW*yuvH*3/2, yuv_f);
        if (size<yuvW*yuvH*3/2) {
            printf("fread fail \n");
            break;
        }
        for (int i=0; i<yuvH; i++) {
            memcpy(frame->data[0] + i*frame->linesize[0], yuv_buffer + yuvW*i, yuvW);
        }
        for (int i=0; i<yuvH/2; i++) {
            memcpy(frame->data[1] + (i*frame->linesize[1]), yuv_buffer+yuvH*yuvW + yuvW/2*i, yuvW/2);
        }
        for (int i=0; i<yuvH/2; i++) {
            memcpy(frame->data[2] + (i*frame->linesize[2]), yuv_buffer+yuvH*yuvW*5/4 + yuvW/2*i, yuvW/2);
        }
        frame->pts = pts_i++;
        ret = avcodec_send_frame(dec_ctx, frame);
        if (ret<0) {
            printf("avcodec_send_frame fail \n");
            break;
        }
        while (1) {
            ret = avcodec_receive_packet(dec_ctx, pkt);
            if (ret==AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret<0) {
                printf("avcodec_receive_packet fail \n");
                break;
            }
            ret = av_interleaved_write_frame(ofmt_ctx, pkt);
            if (ret<0) {
                printf("av_interleaved_write_frame fail \n");
                break;
            }
            av_packet_unref(pkt);
        }
    }
    ret = avcodec_send_frame(dec_ctx, NULL);
    if (ret<0) {
        printf("avcodec_send_frame fail \n");
        goto __FAIL;
    }
    while (1) {
        ret = avcodec_receive_packet(dec_ctx, pkt);
        if (ret==AVERROR(EINVAL) || ret == AVERROR_EOF) {
            break;
        } else if (ret<0) {
            printf("avcodec_receive_packet fail \n");
            break;
        }
        ret = av_interleaved_write_frame(ofmt_ctx, pkt);
        if (ret<0) {
            printf("av_interleaved_write_frame fail \n");
            break;
        }
        av_packet_unref(pkt);
    }
    ret = av_write_trailer(ofmt_ctx);
    if (ret<0) {
        printf("av_write_trailer fail \n");
    }
__FAIL:
    if (ofmt_ctx->pb) {
        avio_close(ofmt_ctx->pb);
    }
    if (dec_ctx) {
        avcodec_close(dec_ctx);
    }
    if (yuv_buffer) {
        av_free(yuv_buffer);
    }
    if (ofmt_ctx) {
        avformat_free_context(ofmt_ctx);
    }
    if (frame) {
        av_frame_free(&frame);
    }
    if (pkt) {
        av_packet_free(&pkt);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容