音视频解封装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 这个检查…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
