美文网首页
【FFmpeg】YUV数据编码成H264

【FFmpeg】YUV数据编码成H264

作者: iOS开发之FFmpeg | 来源:发表于2021-04-22 15:22 被阅读0次

    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);
        }
    }
    

    相关文章

      网友评论

          本文标题:【FFmpeg】YUV数据编码成H264

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