音视频解封装demo:使用libmp4v2解封装(demux)出mp4文件中的h264视频数据和aac语音数据
1、README
前言
本demo是使用的mp4v2来将mp4文件解封装得到h264、aac的,目前demo提供的.a静态库文件是在x86_64架构的Ubuntu16.04编译得到的,如果想在其他环境下测试demo,可以自行编译mp4v2并替换相应的库文件(libmp4v2.a)。
a. 编译
$ make # 或者`make DEBUG=1`打开调试打印信息
如果想编译mp4v2,则可以参考以下步骤:
mp4v2源码下载地址:https://github.com/TechSmith/mp4v2
$ 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的解封装。
$ ./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. 参考文章
-
01.mp4v2应用—mp4转h264 - wade_linux - 博客园.mhtml
-
mp4文件格式解析 - 简书
-
MP4格式详解_DONGHONGBAI的专栏-CSDN博客
-
使用mp4v2解码mp4转成h264码流和aac码流_lq496387202的博客-CSDN博客_mp4v2解码
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
#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...)
#endifint 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);// 获取spsfor (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);// 获取ppsfor (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的trackIDif(videoTrackId >= 0){getH264Stream(mp4Handler, videoTrackId, videoSampleCnt, videoFileName); }if(audioTrackId >= 0){getAACStream(mp4Handler, audioTrackId, audioSampleCnt, audioFileName);}// 需要mp4close 否则在嵌入式设备打开mp4上多了会内存泄露挂掉.MP4Close(mp4Handler, 0);return 0;
}
3、demo下载地址(任选一个)
- https://download.csdn.net/download/weixin_44498318/89526730
- https://gitee.com/linriming/av_mp4_unpack_with_mp4v2.git
- https://github.com/linriming20/av_mp4_unpack_with_mp4v2.git
相关文章:
音视频解封装demo:使用libmp4v2解封装(demux)出mp4文件中的h264视频数据和aac语音数据
1、README 前言 本demo是使用的mp4v2来将mp4文件解封装得到h264、aac的,目前demo提供的.a静态库文件是在x86_64架构的Ubuntu16.04编译得到的,如果想在其他环境下测试demo,可以自行编译mp4v2并替换相应的库文件(libmp4v2.a&#…...
手撸俄罗斯方块(一)——简单介绍
手撸俄罗斯方块 简单介绍 《俄罗斯方块》(俄语:Тетрис,英语:Tetris),是1980年末期至1990年代初期风靡全世界的电脑游戏,是落下型益智游戏的始祖,电子游戏领域的代表作之一&a…...
构建LangChain应用程序的示例代码:61、如何使用 LangChain 和 LangSmith 优化链
本示例介绍如何使用 LangChain 和 LangSmith 优化链。 设置 我们将为 LangSmith 设置环境变量,并加载相关数据 import osos.environ["LANGCHAIN_PROJECT"] "movie-qa" # 设置 LANGCHAIN_PROJECT 环境变量为 "movie-qa"import pan…...
Android系统通过属性设置来控制log输出的方案
Android系统通过属性设置来控制log输出的方案 背景 项目中经常需要在针对性的模块或者文件,分析问题的时候输出Log,但问题分析完成后,又由于性能问题,需要关闭这些log输出。当前大多数情况下是控制整个系统的log等级来实现&#…...
JavaDoc的最佳实践
文章目录 一、JavaDoc 使用说明1.1 什么是 JavaDoc1.2 文档注释结构1.3 常见的 Javadoc 标签 二、文档最佳实践2.1 注释原则2.2 实际案例 参考资料 一、JavaDoc 使用说明 1.1 什么是 JavaDoc JavaDoc 是一款能根据源代码中的文档注释来产生 HTML 格式的 API 文档的工具。 Jav…...
数字力量助西部职教全面提升——唯众品牌大数据、人工智能系列产品中标甘肃庆阳职院数字经济人才培养基地!
近日,唯众品牌凭借在大数据和人工智能领域深耕多年的技术积累和卓越产品,成功中标庆阳职业技术学院全国一体化算力网络国家枢纽节点数字经济人才培养基地项目,标志着唯众在助力西部职业教育与数字经济融合发展的新征程上迈出了坚实的一步。 …...
Swagger的原理及应用详解(四)
本系列文章简介: 在当今快速发展的软件开发领域,特别是随着微服务架构和前后端分离开发模式的普及,API(Application Programming Interface,应用程序编程接口)的设计与管理变得愈发重要。一个清晰、准确且易于理解的API文档不仅能够提升开发效率,还能促进前后端开发者之…...
Elasticsearch7.10集群搭建
Elasticsearch详细介绍: Elasticsearch 是一个分布式、RESTful 风格的搜索和分析引擎。它的核心基于 Apache Lucene,能够处理海量的数据,并支持实时的全文搜索。以下是关于 Elasticsearch 的详细介绍。 一、基本概念 索引(Index…...
SMU Summer 2024 Contest Round 3
A.Hcode OnlineJudge 先用欧拉筛把质数预处理出来,然后枚举左端点的质数,只需要询问右端点是不是质数并取差值的min就行了 #include<bits/stdc.h> #define endl \n #define mk make_pair #define int long long using namespace std; typedef lon…...
uniapp 封装瀑布流组件
思路: 1.coulumns:需要分成几列 2.如何分布数据 3.计算每列的宽度 4.图片进行高度自适应 <template><view :style"{ margin: boxM }"><view class"flex flex-justify-start bg-red" style"background-colo…...
pd虚拟机去虚拟化是什么意思?pd虚拟机去虚拟化教程 PD虚拟机优化设置
Parallels Desktop for Mac(PD虚拟机)去虚拟化是指在虚拟机(Virtual Machine,简称 VM)中禁用或减少虚拟化层的影响,使其表现更接近于物理机。这种操作通常用于提高虚拟机的性能或解决某些软件兼容性问题。具…...
低代码研发项目管理流程优化:提效与创新的双重驱动
随着信息技术的迅猛发展,软件项目的规模和复杂度日益增加,传统的软件开发方式已经难以满足快速迭代和高效交付的需求。在这一背景下,低代码平台应运而生,以其高效、灵活、易用的特点,迅速成为软件行业的新宠。然而&…...
32位版 C 库函数time 将在 2038 年溢出,那到时候,它该何去何从
简单地说,通常不必担心,在64位操作系统已经成为主流的今天这基本上不是问题(在写这篇回答的时候,我才发现我甚至找不到32位的机器来测试)刚好我有一些资料,是我根据网友给的问题精心整理了一份「32库函数的…...
C语言 printf函数缓冲机制
printf不立即打印到stdout的原因 printf函数使用了缓冲机制。当我们调用printf时,输出通常不会立即显示在屏幕上,而是先存储在一个缓冲区中。这是为了提高I/O操作的效率。 缓存数据输出的原理 stdio库维护了一个缓冲区。当缓冲区满了,或者在特定条件下,缓冲区的内容会被刷新…...
【Linux进阶】文件系统8——硬链接和符号连接:ln
在Linux下面的链接文件有两种, 一种是类似Windows的快捷方式功能的文件,可以让你快速地链接到目标文件(或目录);另一种则是通过文件系统的inode 链接来产生新文件名,而不是产生新文件,这种称为硬链接&…...
代码随想录算法训练营Day64|拓扑排序(卡码网117)、dijkstra朴素版
拓扑排序 117. 软件构建 (kamacoder.com) 拓扑排序简单的说是将一个有向图转为线性的排序。 它将图中的所有结点排序成一个线性序列,使得对于任何的边uv,结点u在序列中都出现在结点v之前,这样的序列满足图中所有的前驱-后继关系。 拓扑排…...
neo4j 图数据库:Cypher 查询语言、医学知识图谱
neo4j 图数据库:Cypher 查询语言、医学知识图谱 Cypher 查询语言创建数据查询数据查询并返回所有节点查询并返回所有带有特定标签的节点查询特定属性的节点及其所有关系和关系的另一端节点查询从名为“小明”的节点到名为“小红”的节点的路径 更新数据更新一个节点…...
数据结构基础--------【二叉树基础】
二叉树基础 二叉树是一种常见的数据结构,由节点组成,每个节点最多有两个子节点,左子节点和右子节点。二叉树可以用来表示许多实际问题,如计算机程序中的表达式、组织结构等。以下是一些二叉树的概念: 二叉树的深度&a…...
数据开源 | Magic Data大模型高质量十万轮对话数据集
能够自然的与人类进行聊天交谈,是现今的大语言模型 (LLM) 区别于传统语言模型的重要能力之一,近日OpenAI推出的GPT-4o给我们展示了这样的可能性。 对话于人类来说是与生俱来的,但构建具备对话能力的大模型是一项不小的挑战,收集高…...
webpack之ts打包
tsconfig.json配置 // 是否对js文件进行编译,默认false"allowJs": true,// 是否检查js代码是否符合语法规范,默认false(引入的外部文件有可能语法有问题)"checkJs": true, allowJs和checkJs基本是同时出现,因为有了allowJs 这个检查…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
