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