前言:

mp4文件目前已经成为了流媒体音视频行业的通用标准文件格式,它是基于mov格式基础上演变来的,特别适合多平台播放,录制一次,多个平台都可使用。但是,由于mp4格式相对比较复杂,直到mp4v2这个开源工程的出现,解决了这个问题。

通常,我们在使用mp4文件时,会遇到两个问题:如何从已有的mp4文件中抽取音视频数据帧;如何将音视频数据帧录制成mp4文件,并保持音视频同步。

首先,我们需要对mp4v2开源库进行编译,源码下载地址如下:

CSDN:https://download.csdn.net/download/haoyitech/10290353

mp4v2的开源库提供多种平台的编译,我们要在windows下做演示,选择vs2010编译,vs2010的编译工程又提供多个编译输出,为了避免动态链接库带来的麻烦,我们选择静态lib的方式,调试版:Debug static(MTD),发行版:Release static(MT)。静态lib库的好处是,不用考虑dll加载路径。

mp4v2\build32\bin\Debug Static (MTd)\libmp4v2D.lib
    mp4v2\build32\bin\Release Static (MT)\libmp4v2.lib

源码下载:

CSDN:https://download.csdn.net/download/haoyitech/10291430

源码说明:

开发工具:下载后,请用 VS2010 打开。

总体思路:准备好标准mp4文件(H264+AAC),使用mp4v2提供的标准API,解析出音视频格式信息,视频需要获取关键的PPS和SPS,音频需要获取采样率、声道、扩展信息等等。

测试需要的mp4文件  => sample_libmp4v2\bin\sample.mp4
mp4v2需要的头文件  => sample_libmp4v2\libmp4v2
mp4v2静态调试库    => sample_libmp4v2\libmp4v2\libmp4v2D.lib
mp4v2静态发行库    => sample_libmp4v2\libmp4v2\libmp4v2.lib
浩一科技代码辅助库 => sample_libmp4v2\common

注意:在vs2010工程配置当中,需要在预处理定义中加入 MP4V2_EXPORTS,否则,会在编译中出现链接失败的问题。

关键代码:(详见Csample_libmp4v2Dlg)

1、mp4v2提供的调试回调演示:

void myMP4LogCallback( MP4LogLevel loglevel, const char* fmt, va_list ap)
{// 组合传递过来的格式...CString strDebug;strDebug.FormatV(fmt, ap);if( (strDebug.ReverseFind('\r') < 0) && (strDebug.ReverseFind('\n') < 0) ) {strDebug.Append("\r\n");}// 进行格式转换,并打印出来...string strANSI = CUtilTool::UTF8_ANSI(strDebug);TRACE(strANSI.c_str());
}// 解析mp4文件...
void Csample_libmp4v2Dlg::OnBnClickedButtonParse()
{// 设置MP4调试级别 => 最高最详细级别...MP4LogLevel theLevel = MP4LogGetLevel();MP4LogSetLevel(MP4_LOG_VERBOSE4);MP4SetLogCallback(myMP4LogCallback);// 打开MP4文件...CString strMP4File;MP4FileHandle hMP4Handle = MP4_INVALID_FILE_HANDLE;strMP4File.Format("%s\\sample.mp4", CUtilTool::GetExePath((HINSTANCE)NULL).c_str());string strUTF8 = CUtilTool::ANSI_UTF8(strMP4File);hMP4Handle = MP4Read( strUTF8.c_str() );MP4Close(hMP4Handle);
}

说明:这个调试输出,可以帮助在分析特殊mp4文件时使用,会打印出每一个解析mp4过程的细节,级别越高越详细。

2、有关mp4路径问题:

libmp4v2在文件路径上,需要使用完整的UTF8格式的全路径,因此,我们借助了辅助库来完成。
注意:在工程配置上我们选择的是 多字符集(ANSI)。
CString strMP4File;
MP4FileHandle hMP4Handle = MP4_INVALID_FILE_HANDLE;
strMP4File.Format("%s\\sample.mp4", CUtilTool::GetExePath((HINSTANCE)NULL).c_str());
string strUTF8 = CUtilTool::ANSI_UTF8(strMP4File);
hMP4Handle = MP4Read( strUTF8.c_str() );
string CUtilTool::ANSI_UTF8(LPCTSTR lpSValue)
{string strUValue;USES_CONVERSION;if( lpSValue == NULL || strlen(lpSValue) <= 0 )return strUValue;    // 验证有效性_acp = CP_ACP;                  // 设置code page(默认)// A2W 在某些系统下会发生崩溃...// LPCWSTR lpWValue = A2W(lpSValue); // ANSI to Unicode Wide charint dwSize = MultiByteToWideChar(_acp, 0, lpSValue, -1, NULL, 0);wchar_t * lpWValue = new wchar_t[dwSize];MultiByteToWideChar(_acp, 0, lpSValue, -1, lpWValue, dwSize);_acp = CP_UTF8;                     // 设置code page, 得到编码长度, Unicode Wide char to UTF-8int ret = WideCharToMultiByte(_acp, 0, lpWValue, -1, NULL, NULL, NULL, NULL);strUValue.resize(ret); ASSERT( ret == strUValue.size() );ret = WideCharToMultiByte(_acp, 0, lpWValue, -1, (LPSTR)strUValue.c_str(), strUValue.size(), NULL, NULL);ASSERT( ret == strUValue.size() );delete [] lpWValue;return strUValue;
}

3、解析音视频格式信息头:

bool Csample_libmp4v2Dlg::doMP4ParseAV(MP4FileHandle inFile)
{if( inFile == MP4_INVALID_FILE_HANDLE )return false;ASSERT( inFile != MP4_INVALID_FILE_HANDLE );// 首先获取文件的每秒刻度数和总刻度数(不是毫秒数)...uint32_t dwFileScale = MP4GetTimeScale(inFile);MP4Duration theDuration = MP4GetDuration(inFile);TRACE("=== 文件每秒刻度数:%lu ===\n", dwFileScale);TRACE("=== 文件总刻度数:%lu ===\n", theDuration);// 总毫秒数 = 总刻度数*1000/每秒刻度数 => 先乘法可以降低误差...uint32_t     dwMP4Duration = theDuration*1000/dwFileScale;TRACE("=== 总毫秒数 = 文件总刻度数*1000/文件每秒刻度数 => %lu ===\n", dwMP4Duration);MP4TrackId       tidVideo = MP4_INVALID_TRACK_ID;   // 视频轨道编号MP4TrackId     tidAudio = MP4_INVALID_TRACK_ID;   // 音频轨道编号string         strSPS, strPPS, strAES;int              audio_rate_index = 0;int               audio_channel_num = 0;int              audio_type = 0;uint32_t        audio_time_scale = 0;uint32_t      video_time_scale = 0;// 获取需要的相关信息...uint32_t trackCount = MP4GetNumberOfTracks( inFile );TRACE("=== 发现轨道数:%lu ===\n", trackCount);for( uint32_t i = 0; i < trackCount; ++i ) {MP4TrackId  id = MP4FindTrackId( inFile, i );const char* type = MP4GetTrackType( inFile, id );if( MP4_IS_VIDEO_TRACK_TYPE( type ) ) {// 视频已经有效,检测下一个...if( tidVideo > 0 )continue;// 获取视频信息...tidVideo = id;TRACE("=== 视频轨道号:%d,标识:%s ===\n", tidVideo, type);char * lpVideoInfo = MP4Info(inFile, id);TRACE("视频信息:%s \n", lpVideoInfo);free(lpVideoInfo);// 获取视频时间间隔...video_time_scale = MP4GetTrackTimeScale(inFile, id);TRACE("=== 视频每秒刻度数:%lu ===\n", video_time_scale);const char * video_name = MP4GetTrackMediaDataName(inFile, id);TRACE("=== 视频编码:%s ===\n", video_name);// 获取视频的PPS/SPS信息...uint8_t  ** spsHeader = NULL;uint8_t  ** ppsHeader = NULL;uint32_t  * spsSize = NULL;uint32_t  * ppsSize = NULL;uint32_t    ix = 0;bool bResult = MP4GetTrackH264SeqPictHeaders(inFile, id, &spsHeader, &spsSize, &ppsHeader, &ppsSize);for(ix = 0; spsSize[ix] != 0; ++ix) {// SPS指针和长度...uint8_t * lpSPS = spsHeader[ix];uint32_t  nSize = spsSize[ix];// 只存储第一个SPS信息...if( strSPS.size() <= 0 && nSize > 0 ) {strSPS.assign((char*)lpSPS, nSize);}free(spsHeader[ix]);TRACE("=== 找到第 %d 个SPS格式头,长度:%lu ===\n", ix+1, nSize);}free(spsHeader);free(spsSize);for(ix = 0; ppsSize[ix] != 0; ++ix) {// PPS指针和长度...uint8_t * lpPPS = ppsHeader[ix];uint32_t  nSize = ppsSize[ix];// 只存储第一个PPS信息...if( strPPS.size() <= 0 && nSize > 0 ) {strPPS.assign((char*)lpPPS, nSize);}free(ppsHeader[ix]);TRACE("=== 找到第 %d 个PPS格式头,长度:%lu ===\n", ix+1, nSize);}free(ppsHeader);free(ppsSize);} else if( MP4_IS_AUDIO_TRACK_TYPE( type ) ) {// 音频已经有效,检测下一个...if( tidAudio > 0 )continue;// 获取音频信息...tidAudio = id;TRACE("=== 音频轨道号:%d,标识:%s ===\n", tidVideo, type);char * lpAudioInfo = MP4Info(inFile, id);TRACE("音频信息:%s \n", lpAudioInfo);free(lpAudioInfo);// 获取音频的类型/名称/采样率/声道信息...audio_type = MP4GetTrackAudioMpeg4Type(inFile, id);TRACE("=== 音频格式类型:%d ===\n", audio_type);audio_channel_num = MP4GetTrackAudioChannels(inFile, id);TRACE("=== 音频声道数:%d ===\n", audio_channel_num);const char * audio_name = MP4GetTrackMediaDataName(inFile, id);TRACE("=== 音频编码:%s ===\n", audio_name);audio_time_scale = MP4GetTrackTimeScale(inFile, id);TRACE("=== 音频每秒刻度数(采样率):%lu ===\n", audio_time_scale);if (audio_time_scale == 48000)audio_rate_index = 0x03;else if (audio_time_scale == 44100)audio_rate_index = 0x04;else if (audio_time_scale == 32000)audio_rate_index = 0x05;else if (audio_time_scale == 24000)audio_rate_index = 0x06;else if (audio_time_scale == 22050)audio_rate_index = 0x07;else if (audio_time_scale == 16000)audio_rate_index = 0x08;else if (audio_time_scale == 12000)audio_rate_index = 0x09;else if (audio_time_scale == 11025)audio_rate_index = 0x0a;else if (audio_time_scale == 8000)audio_rate_index = 0x0b;TRACE("=== 音频采样率编号:%lu ===\n", audio_rate_index);// 获取音频扩展信息...uint8_t  * pAES = NULL;uint32_t   nSize = 0;bool haveEs = MP4GetTrackESConfiguration(inFile, id, &pAES, &nSize);// 存储音频扩展信息...if( strAES.size() <= 0 && nSize > 0 ) {strAES.assign((char*)pAES, nSize);}TRACE("=== 音频扩展信息长度:%lu ===\n", nSize);// 释放分配的缓存...if( pAES != NULL ) {free(pAES);pAES = NULL;}}}// 如果音频和视频都没有,返回失败...if((tidVideo == MP4_INVALID_TRACK_ID) && (tidAudio == MP4_INVALID_TRACK_ID)) {MsgLogGM(GM_File_Read_Err);return false;}// 一切正常,循环抽取音视频数据帧...return this->doMP4ReadFrame(inFile, tidVideo, tidAudio);
}

注意:这里有3个每秒刻度数,文件每秒刻度数,视频每秒刻度数,音频每秒刻度数(采样率),越大说明粒度越系,更精确。这个每秒刻度数用来还原数据帧的时间戳很关键。

4、循环抽取并打印音视频数据帧:

// 循环抽取音视频数据帧...
bool Csample_libmp4v2Dlg::doMP4ReadFrame(MP4FileHandle inFile, MP4TrackId inVideoID, MP4TrackId inAudioID)
{BOOL       bAudioComplete = false;BOOL        bVideoComplete = false;uint32_t    dwOutVSendTime = 0;uint32_t    dwOutASendTime = 0;uint32_t    iVSampleInx = 1;uint32_t   iASampleInx = 1;while( true ) {// 读取一帧视频帧...if( !bVideoComplete && !this->doMP4ReadOneFrame(inFile, inVideoID, iVSampleInx++, true, dwOutVSendTime) ) {bVideoComplete = true;continue;}// 读取一帧音频帧...if( !bAudioComplete && !this->doMP4ReadOneFrame(inFile, inAudioID, iASampleInx++, false, dwOutASendTime) ) {bAudioComplete = true;continue;}// 如果音频和视频全部结束,退出循环...if( bVideoComplete && bAudioComplete ) {TRACE("=== MP4文件数据帧读取完毕 ===\n");TRACE("=== 已读取视频帧:%lu ===\n", iVSampleInx);TRACE("=== 已读取音频帧:%lu ===\n", iASampleInx);break;}}return true;
}// 抽取一帧音频或视频数据帧...
bool Csample_libmp4v2Dlg::doMP4ReadOneFrame(MP4FileHandle inFile, MP4TrackId tid, uint32_t sid, bool bIsVideo, uint32_t & outSendTime)
{uint8_t        *   pSampleData = NULL;        // 帧数据指针uint32_t        nSampleSize = 0;       // 帧数据长度MP4Timestamp    nStartTime = 0;            // 开始时间MP4Duration      nDuration = 0;         // 持续时间MP4Duration     nOffset = 0;            // 偏移时间bool         bIsKeyFrame = false;   // 是否关键帧uint32_t        timescale = 0;uint64_t     msectime = 0;// 读取轨道的采样率 和 当前帧的时间戳...timescale = MP4GetTrackTimeScale( inFile, tid );msectime = MP4GetSampleTime( inFile, tid, sid );// 读取一帧数据帧,失败,直接返回...if( false == MP4ReadSample(inFile, tid, sid, &pSampleData, &nSampleSize, &nStartTime, &nDuration, &nOffset, &bIsKeyFrame) )return false;// 计算当前读取数据帧的时间戳...// 计算发送时间 => PTS => 刻度时间转换成毫秒...msectime *= UINT64_C( 1000 );msectime /= timescale;// 计算开始时间 => DTS => 刻度时间转换成毫秒...nStartTime *= UINT64_C( 1000 );nStartTime /= timescale;// 计算偏差时间 => CTTS => 刻度时间转换成毫秒...nOffset *= UINT64_C( 1000 );nOffset /= timescale;// 这里需要释放读取的缓冲区...MP4Free(pSampleData);pSampleData = NULL;// 返回发送时间(毫秒) => 已将刻度时间转换成了毫秒...outSendTime = (uint32_t)msectime;// 打印获取的音视频数据帧内容信息...TRACE("[%s] duration = %I64d, offset = %I64d, KeyFrame = %d, SendTime = %lu, StartTime = %I64d, Size = %lu\n", bIsVideo ? "Video" : "Audio", nDuration, nOffset, bIsKeyFrame, outSendTime, nStartTime, nSampleSize);return true;
}

注意:利用mp4v2抽取的一帧(sample)数据,会返回5个时间,都是刻度时间,都要转换成毫秒。

timescale => 每秒刻度数
    PTS  => 显示时间 => msectime*1000 /= timescale
    DTS  => 解码时间 => nStartTime*1000 /= timescale
    CTTS => 偏差时间 => nOffset*1000 /= timescale
    Duration => 这一帧持续时间 => nDuration*1000 /= timescale

更多信息:

************************************************************
 * 浩一科技,提供云监控、云录播的全平台无插件解决方案。
 * 支持按需直播,多点布控,分布式海量存储,动态扩容;
 * 支持微信扫码登录,全平台帐号统一,关联微信小程序;
 * 支持多种数据输入:摄像头IPC、rtmp、rtsp、MP4文件;
 * 支持全实时、全动态、全网页管理,网页前后台兼容IE8;
 * 支持多终端无插件自适应播放,flvjs/hls/rtmp自动适配;
************************************************************
 * 官方网站 => https://myhaoyi.com
 * 技术博客 => http://blog.csdn.net/haoyitech
 * 开源代码 => https://github.com/HaoYiTech/

************************************************************

如何使用mp4v2解析mp4文件,抽取音视频数据帧【源码】【mp4】【NVR】相关推荐

  1. C# 使用SDL2实现Mp4文件播放音视频

    播放音视频的关键:视频的格式是H264,音频的格式是AAC.使用ffmpeg探测流的方式来实现音视频流的解码播放. 数据处理逻辑:H264->YUV     AAC->PCM. SDL2工 ...

  2. C# 使用SDL2实现Mp4文件播放音视频操作

    播放音视频的关键:视频的格式是H264,音频的格式是AAC.使用ffmpeg探测流的方式来实现音视频流的解码播放. 数据处理逻辑:H264->YUV AAC->PCM. SDL2工具类 u ...

  3. python编程实例视屏-python 下载抖音视频示例源码

    [实例简介] 下载抖音视频 [实例截图] [核心代码] #code:utf-8 import requests from bs4 import BeautifulSoup import json se ...

  4. python抖音表白程序代码_python 下载抖音视频示例源码

    [实例简介] 下载抖音视频 [实例截图] [核心代码] #code:utf-8 import requests from bs4 import BeautifulSoup import json se ...

  5. 栈解析html文件,利用栈将html源码解析为节点树

    /// 如何利用栈将html解析成节点树 /// 首先html是由一个个节点组成,最大的节点为节点   她有两个子节点 和 /// 首先我们将压入栈中 再将 压入栈中 遇到出栈  压入栈中 遇到 出栈 ...

  6. 音视频解封装:MP4核心Box详解及H264AAC打包方案

    ​问题背景: 上一篇文章<音视频封装:MP4结构概述和分析工具>让大家看了下MP4的主要结构和推荐了一些分析工具,如果你对MP4没有任何了解,还是先看上文,了解MP4的基本结构,其中还有许 ...

  7. 工具提取MP4中的音视频

    工具提取MP4中的音视频 版权声明:本文为博主原创文章,若需转载请注明出处. 使用工具: 1)http://www.h264encoder.com/ h264视频转换工具 2)http://ffmpe ...

  8. 加mp4文件后js失效_video不能播放mp4的问题(一)

    最近项目中遇到了video标签无法播放mp4的问题,表现如下: IE可正常播放 safari需要点击两次可播放 chrome内核系列都不能播放 原因排除 首先,排除掉代码错误:替换其它可播放的mp4文 ...

  9. AVI文件的音视频数据简析

    AVI文件的音视频数据 如图是使用AtomicBrowser2(AVI)打开的一个AVI文件: AVI文件从其RIFF标识符后跟的'AVI'开始. 其数据格式如下: 视频音频的放置方式 其中LIST ...

最新文章

  1. JavaScript有关的10个怪癖和秘密(转)
  2. 使用python操作常用的库-kafka
  3. react复制内容到剪贴板
  4. 如何搭建html运行环境,搭建真实的运行环境2019.4.22
  5. 相同布局在不同手机上显示不同_不懂响应式,不同尺寸屏幕下的页面很难达到最佳效果...
  6. 怎么读取can报文_【案例】东风天龙“仪表未收到EECU报文”
  7. HTML5实现涂鸦板
  8. 所有铣床行业调研报告 - 市场现状分析与发展前景预测
  9. BSM:左手业务,右手IT
  10. python学习笔记之列表(list)
  11. 蓝桥杯 ALGO-26 算法训练 麦森数
  12. C语言规范标准-C99(中文版) 完整版正式发布
  13. 软件测试的测试数据分析,软件测试结果归纳与分析
  14. C语言【库函数与自定义函数】详解
  15. matlab 泊松分布作图,matlab用一组数据画泊松分布图
  16. 槑图秀秀 (初学JAVA第三篇)
  17. 各种品牌电脑U盘启动快捷键
  18. 海康2017校招C++开发岗位笔试题
  19. shell脚本:遍历指定文件夹下.jar后缀的文件,并备份到目标文件夹
  20. web自定义字体引用与资源压缩

热门文章

  1. 【泛函分析】平衡集和吸收集
  2. HLS 流媒体服务与加解密
  3. 计算机相关的专刊,计算机 | 1区SCI期刊专刊信息1条
  4. 删了 GPL 协议转闭源?法院判决:GPL 协议终身有效
  5. DATAGUARD原理
  6. Swift5代码添加约束
  7. findfirst, findnext
  8. 创意电子学小知识:电位器
  9. mysql strip_strip 命令的用法
  10. 水仙花数c之和语言程序,水仙花数C语言的