网站首页 > 开源技术 正文
本文的内容是解码裸流,即从本地读取h264码流,然后解码成YUV像素数据的过程。
1、FFmpeg视频解码流程
如上图所示是通过FFmpeg进行视频解码的流程。
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
2、 代码实战
2.1、获取解码器
enum AVCodecID audio_codec_id = AV_CODEC_ID_H264;
const AVCodec *codec = avcodec_find_decoder(audio_codec_id);
if (!codec) {
fprintf(stderr, "Codec not found\n");
return;
}
通过调用 avcodec_find_decoder函数根据ID来查找注册的解码器,这里的ID在源码的libavcodec/codec_id.h文件中的AVCodecID枚举中有定义,我们用视频h264解码的ID使用AV_CODEC_ID_H264即可。当然你也可以使用avcodec_find_decoder_by_name函数通过传入解码器的名称来获取解码器,如:avcodec_find_decoder_by_name("libx264")来获取解码器。
2.2、初始化裸流解析器
// 获取裸流的解析器 AVCodecParserContext(数据) + AVCodecParser(方法)
AVCodecParserContext *parser = av_parser_init(codec->id);
if (!parser) {
fprintf(stderr, "Parser not found\n");
return;
}
调用av_parser_init函数来初始化一个裸流的解析器AVCodecParserContext。传入参数解码器的id。
2.3、 创建上下文
// 分配codec上下文
AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
if(!codec_ctx) {
fprintf(stderr, "Could not allocate audio codec context\n");
return;
}
//将解码器和解码器上下文进行关联
int ret = avcodec_open2(codec_ctx, codec, NULL);
if(ret < 0) {
fprintf(stderr, "Could not open codec\n");
return;
}
avcodec_alloc_context3函数初始化一个上下文,为AVCodecContext分配内存,然后调用avcodec_open2函数打开解码器,将解码器和解码器上下文进行关联。
2.4、打开文件
// 打开输入文件
FILE *infile = fopen(in_file, "rb");
if (!infile) {
fprintf(stderr, "Could not open %s\n", in_file);
return;
}
// 打开输出文件
FILE *outfile = fopen(out_file, "wb");
if (!outfile) {
fprintf(stderr, "Could not open %s\n", in_file);
return;
}
in_file是输入文件的路径,即本地h264格式文件的路径, out_file是存储将h264码流解码后得到的视频像素格式yuv420p格式数据的文件路径。
2.5、创建AVPacket和AVFrame
AVPacket *pkt = av_packet_alloc();
if(!pkt) {
fprintf(stderr, "Could not alloc avpacket\n");
return;
}
AVFrame *decoded_frame = av_frame_alloc();
if(!decoded_frame) {
fprintf(stderr, "Could not allocate audio frame\n");
return;
}
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
2.6、读取数据并解码
// AV_INPUT_BUFFER_PADDING_SIZE 在输入比特流结尾的要求附加分配字节的数量上进行解码
uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
uint8_t *data = inbuf;
size_t data_size = 0;
//读取VIDEO_INBUF_SIZE大小的数据到缓存区
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);
while(data_size > 0) {
ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size, data, (int)data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (ret < 0)
{
fprintf(stderr, "Error while parsing\n");
break;
}
data += ret;
data_size -= ret;
if(pkt->size)
decoder(codec_ctx, pkt, decoded_frame, outfile);
if(data_size < VIDEO_REFILL_THRESH) {
memmove(inbuf, data, data_size);
data = inbuf;
size_t len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
if (len > 0)
data_size += len;
}
}
如上代码所示是读取本地数据并进行解码的过程,首先我们创建了一个VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE大小的数据缓存区,加上AV_INPUT_BUFFER_PADDING_SIZE是为了防止某些优化过的reader一次性读取过多导致越界。然后调用fread函数从本地文件中每次读取VIDEO_INBUF_SIZE大小的数据到缓存区中。
av_parser_parse2函数用来解析出一个完整的Packet,是解码处理过程中的核心函数之一。 如下是官方对于该函数的参数说明。
/**
* Parse a packet.
* @param s parser context.
* @param avctx codec context.
* @param poutbuf set to pointer to parsed buffer or NULL if not yet finished.
* @param poutbuf_size set to size of parsed buffer or zero if not yet finished.
* @param buf input buffer.
* @param buf_size buffer size in bytes without the padding. I.e. the full buffer
size is assumed to be buf_size + AV_INPUT_BUFFER_PADDING_SIZE.
To signal EOF, this should be 0 (so that the last frame
can be output).
* @param pts input presentation timestamp.
* @param dts input decoding timestamp.
* @param pos input byte position in stream.
* @return the number of bytes of the input bitstream used.
*/
int av_parser_parse2(AVCodecParserContext *s,
AVCodecContext *avctx,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int64_t pts, int64_t dts,
int64_t pos);
1、s和avctx分别表示解码器和解码器的上下文
2、poutbuf:输出数据地址
3、poutbuf_size:输出数据大小,如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧
4、buf和buf_size分别表示输入数据和输入数据大小
5、函数执行完后返回已经使用的二进制流的数据长度
【更多音视频学习资料,点击下方链接免费领取↓↓,先码住不迷路~】
2.7、解码
decoder方法的代码如下:
static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{
av_strerror(errnum, err_buf, 128);
return err_buf;
}
static void print_video_format(const AVFrame *frame)
{
printf("width: %u\n", frame->width);
printf("height: %u\n", frame->height);
printf("format: %u\n", frame->format);
}
static void decoder(AVCodecContext *codec_ctx, AVPacket *pkt, AVFrame *frame, FILE *out_file){
int ret = avcodec_send_packet(codec_ctx, pkt);
if(ret == AVERROR(EAGAIN))
{
fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
}
else if (ret < 0)
{
fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n", av_get_err(ret), pkt->size);
return;
}
while (ret >= 0) {
ret = avcodec_receive_frame(codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return;
else if (ret < 0)
{
fprintf(stderr, "Error during decoding\n");
return;
}
static int s_print_format = 0;
if(s_print_format == 0){
s_print_format = 1;
print_video_format(frame);
}
// frame->linesize[1] 对齐的问题
// 正确写法 linesize[]代表每行的字节数量,所以每行的偏移是linesize[]
for(int j=0; j<frame->height; j++)
fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, out_file);
for(int j=0; j<frame->height/2; j++)
fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width/2, out_file);
for(int j=0; j<frame->height/2; j++)
fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width/2, out_file);
}
}
2.7.1、函数解析
avcodec_send_packet和avcodec_receive_frame是成双结对出现的。
1、调?avcodec_send_packet()给解码器传?包含原始的压缩数据的AVPacket对象,需要注意的是输?的avpkt-data缓冲区必须?于AV_INPUT_BUFFER_PADDING_SIZE,因为优化的字节流读取器必须?次读取32或者64?特的数据,且在将包发送给解码器的时候,AVCodecContext必须已经通过avcodec_open2打开。输?的参数AVPakcet,通常情况下,输?数据是?个单?的视频帧或者?个完整的?频帧。调?者保留包的原有属性,解码器不会修改包的内容。解码器可能创建对包的引?。如果包没有引?计数将拷??份。跟以往的API不?样,输?的包的数据将被完全地消耗,如果包含有多个帧,要求多次调?avcodec_recvive_frame,直到 avcodec_recvive_frame返回AVERROR(EAGAIN)或AVERROR_EOF。输?参数可以为NULL,或者AVPacket的data域设置为NULL或者size域设置为0,表示将刷新所有的包,意味着数据流已经结束了。第?次发送刷新会总会成功,第?次发送刷新包是没有必要的,并且返回AVERROR_EOF,如果解码器缓存了?些帧,返回?个刷新包,将会返回所有的解码包。
返回值:
0: 表示成功
AVERROR(EAGAIN):当前状态不接受输?,?户必须先使?avcodec_receive_frame() 读取数据帧;
AVERROR_EOF:解码器已刷新,不能再向其发送新包;
AVERROR(EINVAL):没有打开解码器,或者这是?个编码器,或者要求刷新;
AVERRO(ENOMEN):?法将数据包添加到内部队列
2、调用avcodec_receive_frame从解码器返回已解码的输出数据。在?个循环体内去接收数据的输出,即周期性地调?avcodec_receive_frame()来接收输出的数据,直到返回AVERROR(EAGAIN)或其他错误。需要注意的是该函数在执?其他操作之前,函数内部将始终先调?av_frame_unref(frame)进行资源释放。
返回值:
0: 表示成功,返回?个帧
AVERROR(EAGAIN):该状态下没有帧输出,需要使?avcodec_send_packet发送新的packet到解码器;
AVERROR_EOF:解码器已经被完全刷新,不再有输出帧;
AVERROR(EINVAL):编解码器没打开;
2.8、冲刷解码器
pkt->data = NULL;
pkt->size = 0;
decoder(codec_ctx, pkt, decoded_frame, outfile);
2.9、释放资源
fclose(outfile);
fclose(infile);
avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);
2.10、播放yuv文件
解码后我们最终会得到一个yuv格式的文件,可以使用下面指令进行验证输出是否正确。
ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25 out.yuv
这里的768x320是视频数据的分辨率大小,是通过上述函数方法print_video_format获取的。
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
猜你喜欢
- 2024-12-30 ScalersTalk成长会Java小组第7周学习笔记
- 2024-12-30 ffmpeg cv:Mat编码成H265数据流 ffmpeg解码h265码流
- 2024-12-30 拆解五六年前的国产平板,这做工!
- 2024-12-30 avi如何转成mp4?几种把avi转换成mp4格式的方法推荐
- 2024-12-30 H5标签video,如何播放流视频 h5播放flv视频
- 2024-12-30 超实用干货!这可能是史上最全的视频格式详解
- 2024-12-30 什么是闭合GOP和开放GOP? 闭合型和开放型
- 2024-12-30 OpenCV中VideoCapture类打开视频的方式
- 2024-12-30 拆解五六年前的国产平板 国产平板怎么拆开
- 2024-12-30 如何使用PSV播放MP4 视频自动退出怎么办
你 发表评论:
欢迎- 05-16东契奇:DFS训练时喷了我很多垃圾话 我不懂他为什么比赛不这么干
- 05-16这两球很伤!詹姆斯空篮拉杆不中 DFS接里夫斯传球空接也没放进
- 05-16湖人自媒体调查:89%球迷希望DFS回归79%希望詹姆斯回归
- 05-16Shams:湖人得到全能球员DFS 节省了1500万奢侈税&薪金空间更灵活
- 05-16G5湖人胜率更高!詹姆斯不满判罚,DFS谈5人打满下半场:这很艰难
- 05-16DFS:当东契奇进入状态 所有防守者在他面前都像个圆锥桶
- 05-16上一场9中6!DFS:不能让纳兹-里德这样的球员那么轻松地投三分
- 05-16WIDER FACE评测结果出炉:滴滴人脸检测DFS算法获世界第一
- 最近发表
-
- 东契奇:DFS训练时喷了我很多垃圾话 我不懂他为什么比赛不这么干
- 这两球很伤!詹姆斯空篮拉杆不中 DFS接里夫斯传球空接也没放进
- 湖人自媒体调查:89%球迷希望DFS回归79%希望詹姆斯回归
- Shams:湖人得到全能球员DFS 节省了1500万奢侈税&薪金空间更灵活
- G5湖人胜率更高!詹姆斯不满判罚,DFS谈5人打满下半场:这很艰难
- DFS:当东契奇进入状态 所有防守者在他面前都像个圆锥桶
- 上一场9中6!DFS:不能让纳兹-里德这样的球员那么轻松地投三分
- WIDER FACE评测结果出炉:滴滴人脸检测DFS算法获世界第一
- 湖人自媒体调查:89%球迷希望DFS回归 79%希望詹姆斯回归
- 一觉醒来湖人苦盼的纯3D终于到位 DFS能带给紫金军多少帮助
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)