1)将容器中的音频码流和视频码流分离出来。
2)针对mp4文件中的码流情况进行修复。
#include
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
// MPEG-TS文件解封装得到的码流可直接播放
// MP4/FLV/MKV解封装得到的码流不可播放;
// 这与容器的封装方式有关。
void demuxer(const char *url) {
// 初始化格式上下文
AVFormatContext *fmt_ctx = avformat_alloc_context();
if (fmt_ctx == NULL) {
printf("failed to alloc format context\n");
goto _Error;
}
// 打开输入流
if (avformat_open_input(&fmt_ctx, url, NULL, NULL) < 0) {
printf("failed to open input url\n");
goto _Error;
}
// 读取媒体文件信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
printf("failed to find stream\n");
goto _Error;
}
av_dump_format(fmt_ctx, 0, url, 0);
// 寻找音频流和视频流下标
int video_index = -1, audio_index = -1;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_index = i;
} else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_index = i;
}
}
if (video_index < 0 || audio_index < 0) {
printf("failed to find stream index\n");
goto _Error;
}
// 由打印的视频文件信息确定码流类型
char audiofile[128], videofile[128];
printf("type the name of output audiofile:");
scanf("%s", audiofile);
printf("\ntype the name of output videofile:");
scanf("%s", videofile);
FILE *faudio = fopen(audiofile, "w+");
FILE *fvideo = fopen(videofile, "w+");
AVPacket *packet = av_packet_alloc();
while (av_read_frame(fmt_ctx, packet) == 0) {
if (packet->stream_index == audio_index) {
fwrite(packet->data, 1, packet->size, faudio);
}
else if (packet->stream_index == video_index) {
fwrite(packet->data, 1, packet->size, fvideo);
}
av_packet_unref(packet);
}
_Error:
if (fmt_ctx) avformat_close_input(&fmt_ctx);
if (faudio) fclose(faudio);
if (fvideo) fclose(fvideo);
if (packet) av_packet_free(&packet);
}
int main(int argc, char const* argv[])
{
demuxer(argv[1]);
return 0;
}
编译测试:得到视频信息后,根据文件中的编码信息(mp3或aac,h264或mpeg4,mpeg4码流文件后辍为.m4v),命名解封装的码流文件。
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p,
640x352, 748 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D),
44100 Hz, stereo, fltp, 128 kb/s (default)
MP4封装格式是基于
QuickTime容器
格式定义,媒体描述与媒体数据分开。从MP4得到的H264和AAC码流是ES流,它们缺失解码时必要的起始码
/SPS
/PPS
和adts头
。
我们常规的H264帧数据保存格式是annexb,是具有起始码0x000001或0x00000001;mpeg-ts文件中保存的是视频码流是存在起始码的,而在mp4文件中没有起始码。
H.264视频编码格式主要分为两种形式,即带起始码的H.264码流
和不带起始码的H.264码流
,其中,前者就是我们比较熟悉的H264
、X264;后者就是指AVC1
。
More:MP4中的H264和AAC
怎么修复MP4中的码流?
AAC码流(mp4a):保存一帧码流数据前,补充相应的adts头;
H264码流(avc1):使用h264_mp4toannexb过滤器,对视频码流进行处理。
①每一帧前补充ADTS头
// 生成7字节的ADTS头
char *adts_header_gen(int len) {
static char header[7];
int profile = 2; // AAC LC
int freqidx = 4; // 3 - 48k, 4 - 44.1k
int chncfg = 2; // 声道数量
header[0] = 0xFF;
header[1] = 0xF1;
header[2] = ((profile-1) << 6) | (freqidx << 2) | (chncfg >> 2);
header[3] = ((chncfg & 3) << 6)| (len >> 11);
header[4] = (len & 0x7FF) >> 3;
header[5] = ((len & 0x7) << 5) | 0x1F;
header[6] = 0xFC;
return header;
}
if (packet->stream_index == audio_index) {
// packet->size是adts中数据块的长度
fwrite(adts_header_gen(packet->size+7), 1, 7, faudio);
fwrite(packet->data, 1, packet->size, faudio);
}
More:AAC音频码流解析
②使用h264_mp4toannexb过滤器处理h264码流
// 初始化过滤器
const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
if (bsf == NULL) {
printf("failed to find stream filter\n");
goto _Error;
}
AVBSFContext *bsf_ctx;
av_bsf_alloc(bsf, &bsf_ctx);
avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[video_index]->codecpar);
av_bsf_init(bsf_ctx);
else if (packet->stream_index == video_index) {
if (av_bsf_send_packet(bsf_ctx, packet) == 0) {
while (av_bsf_receive_packet(bsf_ctx, packet) == 0) {
fwrite(packet->data, 1, packet->size, fvideo);
}
}
}
经过比特流过滤器的处理,每个AVPacket的data添加了H.264的NALU的起始码{0,0,0,1};每个IDR帧数据前面添加了SPS和PPS。
More:解析h264视频码流