【FFmpeg】YUV數(shù)據(jù)編碼成H264

YUV是視頻裸數(shù)據(jù)流,沒有被壓縮的,占用內存非常大,不適合在網(wǎng)絡上傳輸,因此咱們平時網(wǎng)絡上傳輸?shù)亩际墙?jīng)過壓縮的視頻流,如咱們平時看的直播都是經(jīng)過編碼壓縮的視頻流,下載到本地之后進行解碼再進行渲染。今天我們就來看一下YUV數(shù)據(jù)是如何編碼成H264數(shù)據(jù)流的。YUV是裸數(shù)據(jù)流,所以咱們要實現(xiàn)編碼,就必須要知道YUV數(shù)據(jù)的格式pix_fmt、視頻寬高。

一 使用命令行進行編碼

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

-f 指定yuv數(shù)據(jù)格式為yuv420p
-s 指定視頻大小為540x960

二 使用代碼進行編碼

1、通過avformat_alloc_output_context2函數(shù)初始化輸出文件上下文

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

2、通過avcodec_find_encoder函數(shù)找到編碼器

dec = avcodec_find_encoder(AV_CODEC_ID_H264)

3、通過avcodec_alloc_context3初始化編碼器上下文

dec_ctx = avcodec_alloc_context3(dec);

4、設置編碼器上下文的參數(shù),包括碼率、時間基、視頻寬高、像素等參數(shù)

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函數(shù)新建一個視頻流,并通過avcodec_parameters_from_context函數(shù)把編碼器的參數(shù)拷貝給視頻流

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

6、通過avcodec_open2函數(shù)打開編碼器

avcodec_open2(dec_ctx, dec, NULL);

7、通過avio_open函數(shù)打開輸出文件

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

8、通過avformat_write_header寫文件頭

avformat_write_header(ofmt_ctx, NULL);

9、循環(huán)從yuv文件中獲取視頻幀,經(jīng)過編碼后,通過av_interleaved_write_frame函數(shù)寫入文件

    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循環(huán),這是因為視頻幀有I、B、P幀的區(qū)別,這就導致可能還有數(shù)據(jù)在緩存中沒有編碼完,因此使用一個while循環(huán)去讀出來。
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);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容