音视频解封装demo:使用libmp4v2解封装(demux)出mp4文件中的h264视频数据和aac语音数据

1、README

前言

本demo是使用的mp4v2来将mp4文件解封装得到h264、aac的,目前demo提供的.a静态库文件是在x86_64架构的Ubuntu16.04编译得到的,如果想在其他环境下测试demo,可以自行编译mp4v2并替换相应的库文件(libmp4v2.a)。

a. 编译
bash 复制代码
$ make # 或者`make DEBUG=1`打开调试打印信息

如果想编译mp4v2,则可以参考以下步骤:

mp4v2源码下载地址:https://github.com/TechSmith/mp4v2

bash 复制代码
$ tar xjf mp4v2-2.0.0.tar.bz2
$ cd mp4v2-2.0.0/
$ ./configure --prefix=$PWD/_install # 交叉编译可参考加上选项: --host=arm-linux-gnueabihf
$ make -j96
$ make install
b. 使用

注:示例2中的音视频测试源文件是不同步的,不影响本demo的解封装。

bash 复制代码
$ ./mp4v2_unpack_demo 
Usage: 
   ./mp4v2_unpack_demo ./avfile/test1.mp4 ./test1_out.h264 ./test1_out.aac
   ./mp4v2_unpack_demo ./avfile/test2.mp4 ./test2_out.h264 ./test2_out.aac
c. 参考文章
d. demo目录结构
复制代码
.
├── avfile
│   ├── test1.mp4
│   ├── test1_out.aac
│   ├── test1_out.h264
│   ├── test2.mp4
│   ├── test2_out.aac
│   └── test2_out.h264
├── docs
│   ├── 01.mp4v2应用---mp4转h264 - wade_linux - 博客园.mhtml
│   ├── mp4文件格式解析 - 简书.mhtml
│   ├── MP4格式详解_DONGHONGBAI的专栏-CSDN博客.mhtml
│   └── 使用mp4v2解码mp4转成h264码流和aac码流_lq496387202的博客-CSDN博客_mp4v2解码.mhtml
├── include
│   └── mp4v2
│       ├── chapter.h
│       ├── file.h
│       ├── file_prop.h
│       ├── general.h
│       ├── isma.h
│       ├── itmf_generic.h
│       ├── itmf_tags.h
│       ├── mp4v2.h
│       ├── platform.h
│       ├── project.h
│       ├── sample.h
│       ├── streaming.h
│       ├── track.h
│       └── track_prop.h
├── lib
│   └── libmp4v2.a
├── main.c
├── Makefile
└── README.md

2、主要代码片段

main.c
c 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "mp4v2/mp4v2.h"

// 编译时Makefile里控制
#ifdef ENABLE_DEBUG
	#define DEBUG(fmt, args...) 	printf(fmt, ##args)
#else
	#define DEBUG(fmt, args...)
#endif


int unpackMp4File(char *mp4FileName, char *videoFileName, char *audioFileName);


unsigned char g_sps[64] = {0};
unsigned char g_pps[64] = {0};
unsigned int  g_spslen  = 0;
unsigned int  g_ppslen  = 0;


int main(int argc, char **argv)
{
	if(argc < 2)
	{
		printf("Usage: \n"
			   "   %s ./avfile/test1.mp4 ./test1_out.h264 ./test1_out.aac\n"
			   "   %s ./avfile/test2.mp4 ./test2_out.h264 ./test2_out.aac\n",
				argv[0], argv[0]);
		return -1;
	}

    int ret = unpackMp4File(argv[1], argv[2], argv[3]);
	if(ret == 0)
	{
		printf("\033[32mSuccess!\033[0m\n");
	}
	else
	{
		printf("\033[31mFailed!\033[0m\n");
	}

    return 0;
}

int getH264Stream(MP4FileHandle mp4Handler, int videoTrackId, int totalSamples, char *saveFileName)
{
	// 调用的接口要传的参数
	uint32_t curFrameIndex = 1; // `MP4ReadSample`函数的参数要求是从1开始,但我们打印帧下标还是从0开始
    uint8_t *pData = NULL;
    uint32_t nSize = 0;
    MP4Timestamp pStartTime;
    MP4Duration pDuration;
    MP4Duration pRenderingOffset;
    bool pIsSyncSample = 0;

	// 写文件要用的参数
    char naluHeader[4] = {0x00, 0x00, 0x00, 0x01};
    FILE *fpVideo = NULL;

    if(!mp4Handler)
		return -1;

    fpVideo = fopen(saveFileName, "wb"); 
	if (fpVideo == NULL)
	{
		printf("open file(%s) error!\n", saveFileName);
		return -1;
	}
 
    while(curFrameIndex <= totalSamples)
    {   
        // 如果传入MP4ReadSample的视频pData是null,它内部就会new 一个内存
        // 如果传入的是已知的内存区域,则需要保证空间bigger then max frames size.
        MP4ReadSample(mp4Handler, videoTrackId, curFrameIndex, &pData, &nSize, &pStartTime, &pDuration, &pRenderingOffset, &pIsSyncSample);

		DEBUG("[\033[35mvideo\033[0m] ");

		if(pIsSyncSample)
		{
			DEBUG("IDR\t");

			fwrite(naluHeader, 4, 1, fpVideo);
			fwrite(g_sps, g_spslen, 1, fpVideo);

			fwrite(naluHeader, 4, 1, fpVideo);
			fwrite(g_pps, g_ppslen, 1, fpVideo);
		}
		else
		{
			DEBUG("SLICE\t");
		}

        if(pData && nSize > 4)
		{
			// `MP4ReadSample`函数的参数要求是从1开始,但我们打印帧下标还是从0开始;而大小已经包含了4字节的start code长度
			DEBUG("frame index: %d\t size: %d\n", curFrameIndex - 1, nSize);
            fwrite(naluHeader, 4, 1, fpVideo);
			fwrite(pData + 4, nSize - 4, 1, fpVideo); // pData+4了,那nSize就要-4
        }
        
        free(pData);
        pData = NULL;
		
        curFrameIndex++;
    }       
    fflush(fpVideo);
    fclose(fpVideo);  
 
    return 0;
}

int getAACStream(MP4FileHandle mp4Handler, int audioTrackId, int totalSamples, char *saveFileName)
{
	// 调用的接口要传的参数
	uint32_t curFrameIndex = 1; // `MP4ReadSample`函数的参数要求是从1开始,但我们打印帧下标还是从0开始
    uint8_t *pData = NULL;
    uint32_t nSize = 0;

	// 写文件要用的参数
	FILE *fpAudio = NULL;

	if(!mp4Handler)
		return -1;

	fpAudio = fopen(saveFileName, "wb");
	if (fpAudio == NULL)
	{
		printf("open file(%s) error!\n", saveFileName);
		return -1;
	}
 
	while(curFrameIndex <= totalSamples)
	{
		// 如果传入MP4ReadSample的音频pData是null,它内部就会new 一个内存
		// 如果传入的是已知的内存区域,则需要保证空间bigger then max frames size.
		MP4ReadSample(mp4Handler, audioTrackId, curFrameIndex, &pData, &nSize, NULL, NULL, NULL, NULL);

		DEBUG("[\033[36maudio\033[0m] ");

		if(pData)
		{			
			DEBUG("frame index: %d\t size: %d\n", curFrameIndex - 1, nSize);
			fwrite(pData, nSize, 1, fpAudio);
		}
		
		free(pData);
		pData = NULL;
		
		curFrameIndex++;
	}		
	fflush(fpAudio);
	fclose(fpAudio);  
 
	return 0;
}


int unpackMp4File(char *mp4FileName, char *videoFileName, char *audioFileName)
{
	MP4FileHandle mp4Handler = 0;
	uint32_t trackCnt = 0;	
	int videoTrackId = -1;
	int audioTrackId = -1;
	unsigned int videoSampleCnt = 0;
	unsigned int audioSampleCnt = 0;


	mp4Handler = MP4Read(mp4FileName);
	if (mp4Handler <= 0)
	{
		printf("MP4Read(%s) error!\n", mp4FileName);
		return -1;
	}
 
	trackCnt = MP4GetNumberOfTracks(mp4Handler, NULL, 0); //获取音视频轨道数
	printf("****************************\n");
	printf("trackCnt: %d\n", trackCnt);
 
    for (int i = 0; i < trackCnt; i++)
    {
		// 获取trackId,判断获取数据类型: 1-获取视频数据,2-获取音频数据
		MP4TrackId trackId = MP4FindTrackId(mp4Handler, i, NULL, 0);
		const char* trackType = MP4GetTrackType(mp4Handler, trackId);

		if (MP4_IS_VIDEO_TRACK_TYPE(trackType))
		{
			// 不关心,只是打印出来看看
			MP4Duration duration = 0;
			uint32_t timescale = 0;

			videoTrackId = trackId;

			duration = MP4GetTrackDuration(mp4Handler, videoTrackId);
			timescale = MP4GetTrackTimeScale(mp4Handler, videoTrackId);
			videoSampleCnt = MP4GetTrackNumberOfSamples(mp4Handler, videoTrackId);
			
			printf("video params: \n"
				   " - trackId: %d\n"
				   " - duration: %lu\n"
				   " - timescale: %d\n"
				   " - samples count: %d\n",
				   videoTrackId, duration, timescale, videoSampleCnt);

			// 读取 sps/pps 
			uint8_t **seqheader;			
			uint32_t *seqheadersize;
			uint8_t **pictheader;
			uint32_t *pictheadersize;

			MP4GetTrackH264SeqPictHeaders(mp4Handler, videoTrackId, &seqheader, &seqheadersize, &pictheader, &pictheadersize);

			// 获取sps
            for (int ix = 0; seqheadersize[ix] != 0; ix++)
            {
				memcpy(g_sps, seqheader[ix], seqheadersize[ix]);
				g_spslen = seqheadersize[ix];
				free(seqheader[ix]);
			}
			free(seqheader);
			free(seqheadersize);

			// 获取pps
			for (int ix = 0; pictheader[ix] != 0; ix++)
			{
				memcpy(g_pps, pictheader[ix], pictheadersize[ix]);
				g_ppslen = pictheadersize[ix];
				free(pictheader[ix]);
			}
			free(pictheader);
			free(pictheadersize);
        }
		else if (MP4_IS_AUDIO_TRACK_TYPE(trackType))
		{
			audioTrackId = trackId;
			audioSampleCnt = MP4GetTrackNumberOfSamples(mp4Handler, audioTrackId);

			printf("audio params: \n"
				   " - trackId: %d\n"
				   " - samples count: %d\n",
				   audioTrackId, audioSampleCnt);
        }
    }
	printf("****************************\n");

    // 解析完了mp4,主要是为了获取sps pps 还有video的trackID
    if(videoTrackId >= 0)
	{
        getH264Stream(mp4Handler, videoTrackId, videoSampleCnt, videoFileName);  
    }

	if(audioTrackId >= 0)
	{
        getAACStream(mp4Handler, audioTrackId, audioSampleCnt, audioFileName);
    }
 
    // 需要mp4close 否则在嵌入式设备打开mp4上多了会内存泄露挂掉.
    MP4Close(mp4Handler, 0);
	
    return 0;
}

3、demo下载地址(任选一个)

相关推荐
别动哪条鱼2 天前
MP4转AAC转换器C++
c++·ffmpeg·音视频·aac
小柯博客14 天前
STM32MP1 没有硬件编解码,如何用 CPU 实现 H.264 编码支持 WebRTC?
c语言·stm32·嵌入式硬件·webrtc·h.264·h264·v4l2
广东数字化转型17 天前
JT808,JT1078 —— AAC编码 —— 部标机语音对讲Java实现
aac·h264·h265·g711a·部标机
Sam Xiao21 天前
JT808,JT1078 —— AAC编码 —— 部标机语音对讲Java实现
aac·h264·h265·g711a·metro·部标机
ftpeak23 天前
《Rust MP4视频技术开发》第八章:生成MP4
开发语言·rust·音视频·mp4
筑凡24 天前
PPT+配音生成带旁白的PPT演示视频
powerpoint·音视频·wps·mp4·ppt
DogDaoDao25 天前
OpenCV音视频编解码器详解
人工智能·opencv·音视频·视频编解码·h264·h265·音视频编解码
极智-9961 个月前
如何录屏?【图文详解】免费录屏软件?电脑如何录屏?电脑怎么录屏?
mp4·如何录屏·免费录屏软件·电脑如何录屏·电脑怎么录屏·screentogif·qq录屏
humors2212 个月前
批量M3U8转MP4工具
ffmpeg·视频·mp4·多媒体·转换·m3u8
小狮子安度因2 个月前
AAC ADTS格式分析
网络·ffmpeg·aac