您当前的位置:首页 > 电脑百科 > 软件技术 > 音/视频编辑

FFmpeg 开发——将视频 YUV 格式编码成 H264

时间:2022-01-14 11:05:24  来源:  作者:海哥科技宅

首先开始的时候我们插入一张雷神大大的图帮助大家理解一下我们今天的操作究竟属于那一步。

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

从上图可以看出我们要做的,就是将像素层的 YUV 格式,编码出编码层的 h264数据。

首先熟悉一下今天我们要用到的 ffmpeg 中的函数和结构体

  • AVFormatContext: 数据文件操作者,主要是用于存储音视频封装格式中包含的信息, 在工程当中占着具足轻重的地位,因为很多函数都要用到它作为参数。同时,它也是我们进行解封装的功能结构体。
  • AVOutputFormat: 输出的格式,包括音频封装格式、视频装格式、字幕封装格式,所有封装格式都在 AVCodecID 这个枚举类型上面了
  • AVStream: 一个装载着视频/音频流信息的结构体,包括音视频流的长度,元数据信息,其中 index 属性用于标识视频/音频流。
  • AVCodecContext: 这个结构体十分庞大,但它的主要是用于编码使用的,结构体中的的 AVCodec *codec 就是编码所采用的编码器器, 当然,这个结构体中要存入视频的基本参数,例如宽高等,存入音频的基本参数,声道,采样率等。
  • AVCodec:编码器,设置编码类型,像素格式,视频宽高,fps(每秒帧数), 用于编解码音视频编码层使用。
  • AVIOContext:用于管理输入输出结构体。例如解码的情况下,将一个视频文件中的数据先从硬盘中读入到结构体中的 buffer 中,然后送给解码器用于解码,后面我们会用到。
  • AVFrame: 结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。

好了,上面就是我们这次解封装用到的结构体的大概解析,那么我们就上代码,好好分析一番。

1、先取个霸气点的函数名,通过输入一个 yuv 文件路径,然后将文件数据进行编码,输出 H264文件。

yuvCodecToVideoH264(const char *input_file_name)

2、打开输入的 yuv 文件, 并设置我们 h264 文件的输出路径,

FILE *in_file = fopen(input_file, "rb");  
// 因为我们在 IOS 工程当中,所以输出路径当然要设置本机的路径了
const char* out_file = [[NSTemporaryDirectory() stringByAppendingPathComponent:@"dash.h264"] cStringUsingEncoding:NSUTF8StringEncoding];

3、获取 yuv 视频中的信息

// 注册 ffmpeg 中的所有的封装、解封装 和 协议等,当然,你也可用以下两个函数代替  
// * @see av_register_input_format()
// * @see av_register_output_format()
 av_register_all();

//  用作之后写入视频帧并编码成 h264,贯穿整个工程当中
AVFormatContext* pFormatCtx;
pFormatCtx = avformat_alloc_context();

// 通过这个函数可以获取输出文件的编码格式, 那么这里我们的 fmt 为 h264 格式(AVOutputFormat *)
fmt = av_guess_format(NULL, out_file, NULL);
pFormatCtx->oformat = fmt;

4、将输出文件中的数据读入到程序的 buffer 当中,方便之后的数据写入,也可以说缓存数据写入

// 打开文件的缓冲区输入输出,flags 标识为  AVIO_FLAG_READ_WRITE ,可读写
if (avio_open(&pFormatCtx->pb,out_file, AVIO_FLAG_READ_WRITE) < 0){
  printf("Failed to open output file! n");
  return;
}

5、创建流媒体数据,规范流媒体的编码格式,设置视频流的 fps

AVStream* video_st;
// 通过媒体文件控制者获取输出文件的流媒体数据,这里 AVCodec * 写 0 , 默认会为我们计算出合适的编码格式
video_st = avformat_new_stream(pFormatCtx, 0);

// 设置 25 帧每秒 ,也就是 fps 为 25
video_st->time_base.num = 1;
video_st->time_base.den = 25;

if (video_st==NULL){
  return ;
}

6、为输出文件设置编码所需要的参数和格式

// 用户存储编码所需的参数格式等等
AVCodecContext* pCodecCtx;

// 从媒体流中获取到编码结构体,他们是一一对应的关系,一个 AVStream 对应一个  AVCodecContext
 pCodecCtx = video_st->codec;
   
// 设置编码器的 id,每一个编码器都对应着自己的 id,例如 h264 的编码 id 就是 AV_CODEC_ID_H264
pCodecCtx->codec_id = fmt->video_codec;

// 设置编码类型为 视频编码
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

// 设置像素格式为 yuv 格式
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;

// 设置视频的宽高
pCodecCtx->width = 480;
pCodecCtx->height = 720;

// 设置比特率,每秒传输多少比特数 bit,比特率越高,传送速度越快,也可以称作码率,
// 视频中的比特是指由模拟信号转换为数字信号后,单位时间内的二进制数据量。
pCodecCtx->bit_rate = 400000;

// 设置图像组层的大小。
// 图像组层是在 MPEG 编码器中存在的概念,图像组包 若干幅图像, 组头包 起始码、GOP 标志等,如视频磁带记录器时间、控制码、B 帧处理码等;
pCodecCtx->gop_size=250;

// 设置 25 帧每秒 ,也就是 fps 为 25
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = 25;

//设置 H264 中相关的参数
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
//pCodecCtx->qcompress = 0.6;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;

// 设置 B 帧最大的数量,B帧为视频图片空间的前后预测帧, B 帧相对于 I、P 帧来说,压缩率比较大,也就是说相同码率的情况下,
// 越多 B 帧的视频,越清晰,现在很多打视频网站的高清视频,就是采用多编码 B 帧去提高清晰度,
// 但同时对于编解码的复杂度比较高,比较消耗性能与时间
pCodecCtx->max_b_frames=3;

// 可选设置
AVDictionary *param = 0;
//H.264
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
// 通过--preset的参数调节编码速度和质量的平衡。
av_dict_set(¶m, "preset", "slow", 0);

// 通过--tune的参数值指定片子的类型,是和视觉优化的参数,或有特别的情况。
// zerolatency: 零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码
av_dict_set(¶m, "tune", "zerolatency", 0);

顺便说一下h264 当中有片组的概念,其中编码片分为5种,I 片、P 片、B 片、SP 片和 SI 片。

ES 码流是 MPEG 码流中的基本流,由视频压缩编码后的视频基 码流(Video ES)和音频压缩编码后的音频基 码流(Audio ES)组成。

相关视频推荐:

音视频开发系列-快速掌握音视频开发基础知识(视频录制原理、视频播放原理、视频基础知识、音频基础知识)_哔哩哔哩_bilibili

音视频学习最佳实践—从FFmpeg到流媒体服务器开发_哔哩哔哩_bilibili

【免费】
FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发-学习视频教程-腾讯课堂

需要更多ffmpeg/webrtc..音视频流媒体开发学习资料加群812855908领取

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

以下顺带一张 ES 码流的结构图片,作为记录学习之用

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

ES 码流采用图像序列(PS)、图像组(GOP)、图像(P)、片(slice)、宏块(MB)、块(B)六层结构。

(1)图像序列层,图像序列包括若干 GOP,序列头包 起始码和序列参数,如档次、级别、彩色图像格式、帧场选择等等;

(2)图像组层,图像组包 若干幅图像,组头包 起始码、GOP 标志等,如视频磁带记录器时间、控制码、B 帧处理码等;

(3)图像层,一幅图像包 若干片,头信息中有起始码、P 标志,如时间、参考帧号、图像类型、MV、分级等;

(4)片层,片是最小的同步单位,包 若干宏块,片头中有起始码、片地址、量化步长等;

(5)宏块层,宏块由 4 个 8×8 亮度块和 2 个色度块组成,宏块头包括宏块地址、宏块类型、运动矢量等。 7、printf(输出) 一些关于输出格式的详细数据,例如时间,比特率,数据流,容器,元数据,辅助数据,编码,时间戳等等

av_dump_format(pFormatCtx, 0, out_file, 1);

8、设置编码器

// 通过 codec_id 找到对应的编码器
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec){
  printf("Can not find encoder! n");
  return;
}

// 打开编码器,并设置参数 param
if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){
  printf("Failed to open encoder! n");
  return;
}

9、设置原始数据 AVFrame

AVFrame *pFrame = av_frame_alloc();// 通过像素格式(这里为 YUV)获取图片的真实大小,例如将 480 * 720 转换成 int 类型int picture_size = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);// 将 picture_size 转换成字节数据,byteunsigned char *picture_buf = (uint8_t *)av_malloc(picture_size);// 设置原始数据 AVFrame 的每一个frame 的图片大小,AVFrame 这里存储着 YUV 非压缩数据avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

10、准备写入数据之前,当然要先写编码的头部了

// 编写 h264 封装格式的文件头部,基本上每种编码都有着自己的格式的头部,想看具体实现的同学可以看看 h264 的具体实现
int ret = avformat_write_header(pFormatCtx,NULL);
if (ret < 0) {
printf("write header is failed");
return;
}

这里顺便记录一下, h264 原始码流,又称为原始码流,都是由一个一个的 NALU 组成的,结构体如下

enum nal_unit_type_e
{
NAL_UNKNOWN = 0, // 未使用
NAL_SLICE = 1, // 不分区、非 IDR 图像的片
NAL_SLICE_DPA = 2, // 片分区 A
NAL_SLICE_DPB = 3, // 片分区 B
NAL_SLICE_DPC = 4, // 片分区 C
NAL_SLICE_IDR = 5, /* ref_idc != 0 / // 序列参数集
NAL_SEI = 6, / ref_idc == 0 / // 图像参数集
NAL_SPS = 7, // 分界符
NAL_PPS = 8, // 序列结束
NAL_AUD = 9, // 码流结束
NAL_FILLER = 12, // 填充
/ ref_idc == 0 for 6,9,10,11,12 */
};
enum nal_priority_e // 优先级
{
NAL_PRIORITY_DISPOSABLE = 0,
NAL_PRIORITY_LOW = 1,
NAL_PRIORITY_HIGH = 2,
NAL_PRIORITY_HIGHEST = 3,
};

typedef struct
{
int startcodeprefix_len; //! 4 for parameter sets and first slice in picture, 3 for everything else (suggested)
unsigned len; //! Length of the NAL unit (Excluding the start code, which does not belong to the NALU)
unsigned max_size; //! Nal Unit Buffer size
int forbidden_bit; //! should be always FALSE
int nal_reference_idc; //! NALU_PRIORITY_xxxx
int nal_unit_type; //! NALU_TYPE_xxxx
char *buf; //! contains the first byte followed by the EBSP
} NALU_t;

11、创建编码后的数据 AVPacket 结构体来存储 AVFrame 编码后生成的数据

AVCodec* pCodec;
av_new_packet(&pkt,picture_size);

>其实从这里看出 AVPacket 跟 AVFrame 的关系如下

编码前:AVFrame

编码后:AVPacket

12、写入 yuv 数据到 AVFrame 结构体中

// 设置 yuv 数据中 y 图的宽高
int y_size = pCodecCtx->width * pCodecCtx->height;

for (int i=0; i<framenum; i++){
//Read raw YUV data
if (fread(picture_buf, 1, y_size3/2, in_file) <= 0){
printf("Failed to read raw data! n");
return ;
}else if(feof(in_file)){
break;
}
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size5/4; // V
//PTS
//pFrame->pts=i;
// 设置这一帧的显示时间
pFrame->pts=i(video_st->time_base.den)/((video_st->time_base.num)25);
int got_picture=0;
// 利用编码器进行编码,将 pFrame 编码后的数据传入 pkt 中
int ret = avcodec_encode_video2(pCodecCtx, &pkt,pFrame, &got_picture);
if(ret < 0){
printf("Failed to encode! n");
return ;
}

// 编码成功后写入 AVPacket 到 输入输出数据操作着 pFormatCtx 中,当然,记得释放内存
if (got_picture==1){
printf("Succeed to encode frame: %5dtsize:%5dn",framecnt,pkt.size);
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
}

13、flush 编码

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){
int ret;
int got_frame;
AVPacket enc_pkt;

// 确认如果
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
      CODEC_CAP_DELAY))
    return 0;
while (1) {
    enc_pkt.data = NULL;
    enc_pkt.size = 0;
    av_init_packet(&enc_pkt);
    ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                 NULL, &got_frame);
    av_frame_free(NULL);
    if (ret < 0)
        break;
    if (!got_frame){
        ret=0;
        break;
    }
    printf("Flush Encoder: Succeed to encode 1 frame!tsize:%5dn",enc_pkt.size);
    /* mux encoded frame */
    ret = av_write_frame(fmt_ctx, &enc_pkt);
    if (ret < 0)
        break;
}
return ret;
}

int ret2 = flush_encoder(pFormatCtx,0);
if (ret2 < 0) {
printf("Flushing encoder failedn");
return;
}

14、我们上面写完了编码头、编码数据,当然也要写入编码的尾部表示结束了啦,这样才是一个完整的编码格式嘛

// 写入数据流尾部到输出文件当中,并释放文件的私有数据
av_write_trailer(pFormatCtx);

15、释放我们之前创建的内存

if (video_st){
// 关闭编码器
avcodec_close(video_st->codec);
// 释放 AVFrame
av_free(pFrame);
// 释放图片 buf,就是 free() 函数,硬要改名字,当然这是跟适应编译环境有关系的
av_free(picture_buf);
}

// 关闭输入数据的缓存
avio_close(pFormatCtx->pb);
// 释放 AVFromatContext 结构体
avformat_free_context(pFormatCtx);

// 关闭输入文件
fclose(in_file);

---- 好了,写到这里,我们首先要做的就是利用就把下面这个 .yuv 文件放到工程当中,如下图

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

然后在 `- (void)viewDidLoad `方法中使用如下代码

const char *input_file = [[[NSBundle mainBundle] pathForResource:@"FFmpegTest" ofType:@"yuv"] cStringUsingEncoding:NSUTF8StringEncoding];

yuvCodecToVideoH264(input_file);

然后运行,瞬间, 利用同步推打开我们工程的系统,看到我们就得到我们想要的东西了

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

---- 有些小伙伴可能在编译的时候遇到错误,那是因为函数当中一些用到的工程库并没有链接到工程中,可以在工程的 General->Linked Frameworks and Libraries 检查如下图

FFmpeg 开发——将视频 YUV 格式编码成 H264

 

好了,先写这么多了



Tags:FFmpeg   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
首先开始的时候我们插入一张雷神大大的图帮助大家理解一下我们今天的操作究竟属于那一步。 从上图可以看出我们要做的,就是将像素层的 YUV 格式,编码出编码层的 h264数据。首...【详细内容】
2022-01-14  Tags: FFmpeg  点击:(0)  评论:(0)  加入收藏
FFmpeg的作用: FFmpeg是一个优秀的多媒体框架; FFmpeg可以运行在Linux,Mac,Windows等平台; FFmpeg可以解码,编码,转码,复用,解复用,过滤音视频数据。播放器基本架构:image.pngSDL视频渲...【详细内容】
2021-10-27  Tags: FFmpeg  点击:(33)  评论:(0)  加入收藏
ffmpeg 常用基本命令资料推荐【免费】FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发-学习视频教程-腾讯课堂1.分离视频音频流ffmpeg -i input_file -vcodec copy -an...【详细内容】
2021-10-15  Tags: FFmpeg  点击:(59)  评论:(0)  加入收藏
一、FFmpeg视频解码器1.视频解码知识1).纯净的视频解码流程压缩编码数据->像素数据。例如解码H.264,就是“H.264码流->YUV”。2).一般的视频解码流程视频码流一般存储在一定...【详细内容】
2021-05-19  Tags: FFmpeg  点击:(165)  评论:(0)  加入收藏
1.前言当我们在在看腾讯视频或者其他网页上的视频时,浏览器会先缓冲ts文件到你本地.如果你想把这些ts转成mp4文件保存在本地,可以使用ffmpeg进行格式转换。准备需要合成mp4而...【详细内容】
2021-04-25  Tags: FFmpeg  点击:(458)  评论:(0)  加入收藏
在实际工作中,通常需要ffmpeg作为工具来验证一个问题,比如播放一个视频,提取一个码流,转码视频,转封格式等,用的时候才发现忘记了相关命令,Google一番花老大的力气才找到自己需要的...【详细内容】
2020-09-27  Tags: FFmpeg  点击:(78)  评论:(0)  加入收藏
1.环境搭建和整体工程说明命令行输入:-i 3.flv -vcodec copy 2_audio.mp4 转码的CPU占有率。 需要在这个目录下,拷贝正确的SDL2.dll 转码过程: 转码成功后,MP4的大小要比3.fl...【详细内容】
2020-09-24  Tags: FFmpeg  点击:(129)  评论:(0)  加入收藏
1.框架分析 ffplay.c是FFmpeg源码⾃带的播放器,调⽤FFmpeg和SDL API实现⼀个⾮常有⽤的播放器。例如哔哩哔哩著名开源项⽬ijkplayer也是基于ffplay.c进⾏⼆次开发。ffplay实...【详细内容】
2020-09-09  Tags: FFmpeg  点击:(105)  评论:(0)  加入收藏
一、前言采用ffmpeg解码,是所有视频监控开发人员必备的技能,绕不过去的一个玩意,甚至可以说是所有音视频开发人员的必备技能。FFmpeg是一套可以用来记录、转换数字音频、视频,并...【详细内容】
2020-08-08  Tags: FFmpeg  点击:(96)  评论:(0)  加入收藏
Linux安装操作系统:CentOS7 ffmpeg版本:3.4.1安装先访问ffmpeg官网下载linux安装包 http://ffmpeg.org/download.html ,这里我下载的是官方已经编译过的版本,别问我为啥不自己编...【详细内容】
2020-06-24  Tags: FFmpeg  点击:(96)  评论:(0)  加入收藏
▌简易百科推荐
首先开始的时候我们插入一张雷神大大的图帮助大家理解一下我们今天的操作究竟属于那一步。 从上图可以看出我们要做的,就是将像素层的 YUV 格式,编码出编码层的 h264数据。首...【详细内容】
2022-01-14  海哥科技宅    Tags:FFmpeg   点击:(0)  评论:(0)  加入收藏
剪映可以剪辑音频,具体操作方法如下:1、打开剪映后,点击“开始创作”。2、在“照片视频”或“素材库”中随便选择一个媒体文件,然后点击添加。建议选择图片,可省去步骤3和步骤4中...【详细内容】
2022-01-10  玩手机的张先生    Tags:剪映   点击:(28)  评论:(0)  加入收藏
m4a和mp3一样,都是一种音频格式,m4a是一种专属于苹果的音频格式,比如iphone手机的录音文件就是m4a格式的,对于苹果手机用户来说并不陌生。而mp3则是一种常见的通用的音频格式,大...【详细内容】
2022-01-10  优速办公软件    Tags:m4a   点击:(11)  评论:(0)  加入收藏
电脑如何mp4转mp3?mp4和mp3分别是常见的视频和音频文件。当我们从网上获取视频或音频时经常能够接触到它们。简单地从网上获取mp4视频和mp3音频,我们都会,但是用电脑如何将mp4...【详细内容】
2022-01-10  娱乐乐小女警    Tags:视频文件   点击:(26)  评论:(0)  加入收藏
很多人想在Windows11电脑上录制电脑上播放的声音或音乐,却不知道如何操作。Win11系统是微软推出的最新系统,很多人购买的电脑上都预装了正版Win11系统,小编购买的联想笔记本电...【详细内容】
2022-01-10  手机技术达人    Tags:录制   点击:(31)  评论:(0)  加入收藏
嗨喽,亲爱的小伙伴们,小编我又来啦!每每收到私信我都很想把我之前分享过的文章转发给这些需要的小伙伴。不过呢,重新换个角度去分享也挺好的,让更多的小伙伴看到小编的分享也不错...【详细内容】
2022-01-10  酷酷的西部牛仔    Tags:视频   点击:(8)  评论:(0)  加入收藏
目前市面上音频文件有各种类型, 虽然我们可能都熟悉 MP3,但 AAC、FLAC、OGG 或 WMA 呢? 为什么存在这么多音频标准? 有没有最好的音频格式? 哪些是重要的,哪些可以忽略?一旦您意识...【详细内容】
2022-01-07  ITBang    Tags:音频格式   点击:(20)  评论:(0)  加入收藏
硬件视频加速(Hardware video acceleration)通过让显卡编解码视频,从而减轻cpu负担并且还能节省电源。当视频内容存储在硬盘、dvd、摄像机、手机或广播媒体上时,通常会被压缩...【详细内容】
2022-01-07  FFmpegWebRTC项目实战    Tags:视频加速   点击:(12)  评论:(0)  加入收藏
今天还真是有点小激动啊,B站看来要和抖音杠上了,之前抖音推出网页端要对标B站,不过似乎大家没有用网页刷抖音的习惯。但是这次B站竟然推出了“必剪”电脑客户端,激动的我赶紧下...【详细内容】
2021-12-14  最佳应用    Tags:剪辑   点击:(53)  评论:(0)  加入收藏
做短视频的话我们需不需要跟着热点走呢?答案当然是肯定的!而且还是一定要的!为什么呢?因为热点是最大的流量池,比如最近的大S离婚事件就是热点,这是最大的流量池。所有人的目光都...【详细内容】
2021-12-01  易撰    Tags:短视频   点击:(38)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条