在使用FFMPEG的类库进行编程的过程中,可以直接输出解复用之后的的视频数据码流。只需要在每次调用av_read_frame()之后将得到的视频的AVPacket存为本地文件即可。

经试验,在分离MPEG2码流的时候,直接存储AVPacket即可。

在分离H.264码流的时候,直接存储AVPacket后的文件可能是不能播放的。

如果视音频复用格式是TS(MPEG2 Transport Stream),直接存储后的文件是可以播放的。

复用格式是FLV,MP4则不行。

经过长时间资料搜索发现,FLV,MP4这些属于“特殊容器”,需要经过以下处理才能得到可播放的H.264码流:

1.第一次存储AVPacket之前需要在前面加上H.264的SPS和PPS。这些信息存储在AVCodecContext的extradata里面。

并且需要使用FFMPEG中的名为"h264_mp4toannexb"的bitstream filter 进行处理。

然后将处理后的extradata存入文件

具体代码如下:(源码见最后)

FILE *fp=fopen("test.264","ab");
AVCodecContext *pCodecCtx=... unsigned char *dummy=NULL; //输入的指针
int dummy_len;
AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("h264_mp4toannexb");
av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, , );
fwrite(pCodecCtx->extradata,pCodecCtx-->extradata_size,,fp);
av_bitstream_filter_close(bsfc);
free(dummy);

2.通过查看FFMPEG源代码我们发现,AVPacket中的数据起始处没有分隔符(0x00000001), 也不是0x65、0x67、0x68、0x41等字节,所以可以AVPacket肯定这不是标准的nalu。其实,AVPacket前4个字表示的是nalu的长度,从第5个字节开始才是nalu的数据。所以直接将AVPacket前4个字节替换为0x00000001即可得到标准的nalu数据。

具体代码如下:

char nal_start[]={,,,};
fwrite(nal_start,,,fp);
fwrite(pkt->data+,pkt->size-,,fp);
fclose(fp);

经过以上两步处理之后,我们就得到了可以正常播放的H.264码流。

3.ffmpeg中提供了一个流过滤器"h264_mp4toannexb"完成这项工作(从extradata中解析出sps及pps),关键代码如下:

 //h264_mp4toannexb_bsf.c
static int h264_mp4toannexb_filter(AVBitStreamFilterContext *bsfc,
AVCodecContext *avctx, const char *args,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int keyframe) {
H264BSFContext *ctx = bsfc->priv_data;
uint8_t unit_type;
int32_t nal_size;
uint32_t cumul_size = ;
const uint8_t *buf_end = buf + buf_size; /* nothing to filter */
if (!avctx->extradata || avctx->extradata_size < ) {
*poutbuf = (uint8_t*) buf;
*poutbuf_size = buf_size;
return ;
} //
//从extradata中分析出SPS、PPS
//
/* retrieve sps and pps NAL units from extradata */
if (!ctx->extradata_parsed) {
uint16_t unit_size;
uint64_t total_size = ;
uint8_t *out = NULL, unit_nb, sps_done = , sps_seen = , pps_seen = ;
const uint8_t *extradata = avctx->extradata+; //跳过前4个字节
static const uint8_t nalu_header[] = {, , , }; /* retrieve length coded size */
ctx->length_size = (*extradata++ & 0x3) + ; //用于指示表示编码数据长度所需字节数
if (ctx->length_size == )
return AVERROR(EINVAL); /* retrieve sps and pps unit(s) */
unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
if (!unit_nb) {
goto pps;
} else {
sps_seen = ;
} while (unit_nb--) {
void *tmp; unit_size = AV_RB16(extradata);
total_size += unit_size+;
if (total_size > INT_MAX - FF_INPUT_BUFFER_PADDING_SIZE ||
extradata++unit_size > avctx->extradata+avctx->extradata_size) {
av_free(out);
return AVERROR(EINVAL);
}
tmp = av_realloc(out, total_size + FF_INPUT_BUFFER_PADDING_SIZE);
if (!tmp) {
av_free(out);
return AVERROR(ENOMEM);
}
out = tmp;
memcpy(out+total_size-unit_size-, nalu_header, );
memcpy(out+total_size-unit_size, extradata+, unit_size);
extradata += +unit_size;
pps:
if (!unit_nb && !sps_done++) {
unit_nb = *extradata++; /* number of pps unit(s) */
if (unit_nb)
pps_seen = ;
}
} if(out)
memset(out + total_size, , FF_INPUT_BUFFER_PADDING_SIZE); if (!sps_seen)
av_log(avctx, AV_LOG_WARNING, "Warning: SPS NALU missing or invalid. The resulting stream may not play.\n");
if (!pps_seen)
av_log(avctx, AV_LOG_WARNING, "Warning: PPS NALU missing or invalid. The resulting stream may not play.\n"); av_free(avctx->extradata);
avctx->extradata = out;
avctx->extradata_size = total_size;
ctx->first_idr = ;
ctx->extradata_parsed = ;
} *poutbuf_size = ;
*poutbuf = NULL;
do {
if (buf + ctx->length_size > buf_end)
goto fail; //buf为NULL时,以下代码将不再执行 //
//用于保存数据长度的字节数,是在分析原extradata计算出来的
//
if (ctx->length_size == ) {
nal_size = buf[];
} else if (ctx->length_size == ) {
nal_size = AV_RB16(buf);
} else
nal_size = AV_RB32(buf); buf += ctx->length_size;
unit_type = *buf & 0x1f; if (buf + nal_size > buf_end || nal_size < )
goto fail; /* prepend only to the first type 5 NAL unit of an IDR picture */
if (ctx->first_idr && unit_type == ) {
//
//copy IDR 帧时,需要将sps及pps一同拷贝
//
if (alloc_and_copy(poutbuf, poutbuf_size,
avctx->extradata, avctx->extradata_size,
buf, nal_size) < )
goto fail;
ctx->first_idr = ;
} else {
//
//非IDR帧,没有sps及pps
if (alloc_and_copy(poutbuf, poutbuf_size,
NULL, ,
buf, nal_size) < )
goto fail;
if (!ctx->first_idr && unit_type == )
ctx->first_idr = ;
} buf += nal_size;
cumul_size += nal_size + ctx->length_size;
} while (cumul_size < buf_size); return ; fail:
av_freep(poutbuf);
*poutbuf_size = ;
return AVERROR(EINVAL);
}

一般情况下,extradata中包含一个sps、一个pps 的nalu, 从上面的代码中容易看出extradata的数据格式。分析后的sps及pps依然储存在extradata域中,并添加了起始符。从代码中还可以看出,上面的函数会将sps、pps及packet中的数据,都copy到poutbuf指示的内存中,如果不需要copy到指定内存,直接给buf参数传入空值即可。

使用FFMPEG类库分离出多媒体文件中的H.264码流的更多相关文章

  1. (转)使用FFMPEG类库分离出多媒体文件中的H.264码流

    出自:http://blog.csdn.net/leixiaohua1020/article/details/11800877   在使用FFMPEG的类库进行编程的过程中,可以直接输出解复用之后的的 ...

  2. 海思3518EV200 SDK中获取和保存H.264码流详解

    /****************************************** step 2: Start to get streams of each channel. ********** ...

  3. 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)

    本文介绍一个最简单的基于FFMPEG的视频编码器.该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据.编码器代码十分简单,可是每一行代码都非常重要,适合好好研究一下.弄清楚了本代码也 ...

  4. 我的Java开发学习之旅------>工具类:Java使用正则表达式分离出字符串中的中文和英文

    今天看到一个工具类使用正则表达式将一大段字符串中的中文和英文都分离出来了,在此记录一下,读者可以收藏! import java.util.ArrayList; import java.util.Col ...

  5. FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  6. FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧间宏块(Inter)

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  7. FFmpeg的H.264解码器源代码简单分析:熵解码(Entropy Decoding)部分

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  8. FFmpeg的H.264解码器源代码简单分析:概述

    ===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...

  9. FFmpeg的H.264解码器源代码简单分析

    本文简单记录FFmpeg中libavcodec的H.264解码器(H.264 Decoder)的源代码.这个H.264解码器十分重要,可以说FFmpeg项目今天可以几乎“垄断”视音频编解码技术,很大一 ...

随机推荐

  1. MonGoDB 在linux 上的安装和配置

    01: 下载 linux 版本的二进制包 => https://www.mongodb.com/ 02: 解压  => tar -zxf mongodb-linux-x86_64-3.4. ...

  2. WIN10下VS CODE 更新后拓展宿主意外终止问题解决方法(每次系统更新都要删除一次git)

    S CODE最近更新后,一打开就会出现拓展宿主意外终止的错误,点重新加载等都不好用. 出现这个问题主要是和git有关,做如下操作处理就好: 打开vscode安装文件夹->resource文件夹- ...

  3. VS2017创建一个 ASP.NET Core2.0 应用,并搭建 MVC 框架

    https://testerhome.com/topics/11747 1.使用最新版本的VS2017,并安装.NET Core2.0中相关开发工具   2.打开VS2017,点击文件-新建-项目,选 ...

  4. js 定义一个对象并且给对象赋值方法

    var  obj={}; obj.a="12"; obj.b="13" var o ={}; o.jsonObj=obj; 取得属性值直接用 obj.a; ob ...

  5. 用python实现一个简单的服务器

    打开命令行工具,输入: python3 -m http.server 8000(端口可以自己定) 通过访问:http://ip:8000/,就能给别人快速分享文件了.

  6. android如何快速查看APK包名和activity

    一.通过ADB命令 1.dos进入 2.输入adb shell登录 3.输入dumpsys package | grep eggs(过滤相关包名) 二.通过日志查看包名() 1.连接设备 2.cmd命 ...

  7. JAVAWEB 一一 Spirng(框架,IOC控制反转,DI依赖注入)

    jar包 applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <bea ...

  8. servlet(1)request常用方法

    HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,开发人员通过这个对象的方法,可以获得客户这些信息. req ...

  9. (转)JPA + SpringData

    jpa + spring data 约定优于配置 convention over configuration http://www.cnblogs.com/crawl/p/7703679.html 原 ...

  10. winform 凹进去的button

    如果是工具栏按钮的话,可以设置CheckState属性为CheckState.Checked,这样就是按下状态了如果是普通按钮的话,有两种方法一种是系统提供的,在工具箱上右键,[选择项],然后在[CO ...