当前位置 博文首页 > parser_parse2)_wanggao的专栏:ffmpeg学习(19)文件流、网络
前文 【ffmpeg学习(17)文件流、网络流的格式解析(使用AVIOContext )】 使用AVIOContext来指定AVFormat的输入,从内存数据中读取音视频数据进行解析。
当我们需要解析网络流时,通常是使用socket手段接收到数据,主动使用ffmpeg进行解析解码。如果继续使用AVIOContext,必须将接收的socket数据复制到AVIOContext的回调中进行处理,或者使用队列进行数据的同步处理,并且要能完整每个数据(因为缓冲区可能不足以放下当前包的所有数据,详见上一篇博客);使用了libavformat库支持解析ffmpeg支持的所有数据格式,但是当网络流格式明确时就不需要使libavformat库以减少移植依赖。
本文介绍仅使用libavcodec库,不依赖libavformat库使用av_parser_parse2
来解析传入的指定编码格式的数据。
AVCodecParser用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。他的核心函数是av_parser_parse2()。它的定义如下所示。
/**
* 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.
*
* Example:
* @code
* while(in_len){
* len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
* in_data, in_len, pts, dts, pos);
* in_data += len;
* in_len -= len;
*
* if(size)
* decode_frame(data, size);
* }
* @endcode
*/
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);
其中poutbuf指向解析后输出的压缩编码数据帧,buf指向输入的压缩编码数据。如果函数执行完后输出数据为空(poutbuf_size为0),则代表解析还没有完成,还需要再次调用av_parser_parse2()解析一部分数据才可以得到解析后的数据帧。当函数执行完后输出数据不为空的时候,代表解析完成,可以将poutbuf中的这帧数据取出来做后续处理。
这里使用每次读取固定长度的数据方式模拟接收到网络数据。
av_parser_parse2()函数将从给定长度的数据中尝试解析完整的一帧压缩编码数据,若给定的数据不足以解析则需要提供更多的数据;如果解析成一帧还有剩余数据,需要处理完当前解析帧之后继续读取剩余数据,尝试解析出下一帧压缩编码数据。
#include <stdio.h>
extern "C"{
#include "libavcodec/avcodec.h"
};
//test different codec
#define TEST_H264 1
#define TEST_HEVC 0
int main(int argc, char* argv[])
{
#if TEST_HEVC
AVCodecID codec_id = AV_CODEC_ID_HEVC;
char filepath_in[] = "bigbuckbunny_480x272.hevc";
#elif TEST_H264
AVCodecID codec_id = AV_CODEC_ID_H264;
//char filepath_in[] = "../../simplest_ffmpeg_decoder_pure/bigbuckbunny_480x272.h264";
//char filepath_in[] = "../../files/BladeRunner2049.h264";
char filepath_in[] = "C:/Users/wanggao/Desktop/111_clyicrow.h264";
//char filepath_in[] = "C:/Users/wanggao/Desktop/111.h264";
#else
AVCodecID codec_id = AV_CODEC_ID_MPEG2VIDEO;
char filepath_in[] = "bigbuckbunny_480x272.m2v";
#endif
char filepath_out[] = "BladeRunner2049.yuv";
int first_time = 1;
int ret;
// 初始化 decoder 和 parser
AVCodecContext *pCodecCtx = NULL;
AVCodecParserContext *pCodecParserCtx = NULL;
//AVCodec *pCodec = avcodec_find_decoder(codec_id);
AVCodec *pCodec = avcodec_find_decoder_by_name("h264_cuvid"); // 硬解码器
if(!pCodec) {
av_log(NULL, AV_LOG_ERROR, "Codec not found\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if(!pCodecCtx) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context\n");
return -1;
}
//codec_id = pCodecCtx->codec_id;
pCodecParserCtx = av_parser_init(codec_id);
if(!pCodecParserCtx) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate video parser context\n");
return -1;
}
//if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
// pCodecCtx->flags|= CODEC_FLAG_TRUNCATED;
if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not open codec\n");
return -1;
}
// 输入输出文件
FILE *fp_in;
FILE *fp_out;
//Input File
fp_in = fopen(filepath_in, "rb");
if(!fp_in) {
printf("Could not open input stream\n");
return -1;
}
//Output File
fp_out = fopen(filepath_out, "wb");
if(!fp_out) {
printf("Could not open output YUV file\n");
return -1;
}
const int in_buffer_size = 4096;
unsigned char in_buffer[in_buffer_size] = {0};
unsigned char *cur_ptr;
int cur_size;
AVFrame *pFrame;
AVPacket packet;
pFrame = av_frame_alloc();
av_init_packet(&packet);
while(1) { // 模拟网络数据到来的回调
cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
if(cur_size == 0)
break;
cur_ptr = in_buffer;
while(cur_size>0) // 剩余数据未进行解析
{
// 尝试从长度为cur_size的数据cur_ptr解析一帧压缩编码数据
int len = av_parser_parse2(pCodecParserCtx, pCodecCtx,
&packet.data, &packet.size,cur_ptr, cur_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
cur_ptr += len;
cur_size -= len;
if(packet.size == 0)
continue; // 未解析出完整一帧
//Some Info from AVCodecParserContext
printf("[Packet]Size:%6d\t", packet.size);
switch(pCodecParserCtx->pict_type) {
case AV_PICTURE_TYPE_I: printf("Type:I\t"); break;
case AV_PICTURE_TYPE_P: printf("Type:P\t"); break;
case AV_PICTURE_TYPE_B: printf("Type:B\t"); break;
default: printf("Type:Other\t"); break;
}
printf("Number:%4d\n", pCodecParserCtx->output_picture_number);
// 解码一帧压缩编码数据为YUV420P图像
ret = avcodec_send_packet(pCodecCtx, &packet);
while(ret >= 0) {
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
else if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
exit;
}
//Y, U, V
for(int i = 0; i < pFrame->height; i++)
fwrite(pFrame->data[0] + pFrame->linesize[0] * i, 1, pFrame->width, fp_out);
//for(int i = 0; i < pFrame->height / 2; i++)
// fwrite(pFrame->data[1] + pFrame->linesize[1] * i, 1, pFrame->width / 2, fp_out);
//for(int i = 0; i < pFrame->height / 2; i++)
// fwrite(pFrame->data[2] + pFrame->linesize[2] * i, 1, pFrame->width / 2, fp_out);
printf("Succeed to decode 1 frame!\n");
}
}
}
// flush (实际网络流中可以忽略)
while(1) {
ret = avcodec_send_packet(pCodecCtx, NULL);
if(ret < 0)
break;
while(ret >= 0) {
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
continue;
}
else if(ret < 0) {
av_log(