ffmpeg 视频文件解封装,提取mp4中的h264码流和aac码流

本次目标:

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),命名解封装的码流文件。
ffmpeg 视频文件解封装,提取mp4中的h264码流和aac码流_第1张图片



mp4文件解封装得到的h264码流和aac码流都不能解码播放!


1、mp4文件中的码流有什么不同

	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/PPSadts头

我们常规的H264帧数据保存格式是annexb,是具有起始码0x000001或0x00000001;mpeg-ts文件中保存的是视频码流是存在起始码的,而在mp4文件中没有起始码。

H.264视频编码格式主要分为两种形式,即带起始码的H.264码流不带起始码的H.264码流,其中,前者就是我们比较熟悉的H264、X264;后者就是指AVC1

More:MP4中的H264和AAC


2、怎么修复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视频码流


视频播放效果:

原mp4文件:
ffmpeg 视频文件解封装,提取mp4中的h264码流和aac码流_第2张图片

out.h264码流文件:
image-20210514112149236

out.aac文件:
ffmpeg 视频文件解封装,提取mp4中的h264码流和aac码流_第3张图片

你可能感兴趣的:(音视频开发,mp4,ffmpeg)