MP4V2封装的类库,可将H264和AAC直接打包到MP4容器中,堪称经典

2024-03-01 02:48

本文主要是介绍MP4V2封装的类库,可将H264和AAC直接打包到MP4容器中,堪称经典,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

废话少说,直接上代码:

// MP4Encoder.h
#pragma once#ifndef _MP4V2_H_
#define _MP4V2_H_
#include "mp4v2/mp4v2.h"
#endif#define MP4ENCODER_ERROR(err) ((MP4EncoderResult)(-(err)))
#define DEFAULT_RECORD_TIME 0Utypedef enum
{MP4ENCODER_ENONE = 0,MP4ENCODER_E_CREATE_FAIL,MP4ENCODER_E_ADD_VIDEO_TRACK,MP4ENCODER_E_ADD_AUDIO_TRACK,MP4ENCODER_WARN_RECORD_OVER,MP4ENCODER_E_WRITE_VIDEO_DATA,MP4ENCODER_E_WRITE_AUDIO_DATA,MP4ENCODER_E_ALLOC_MEMORY_FAILED,MP4ENCODER_E_UNKONOWN
}MP4EncoderResult;class MP4Encoder
{
public:MP4Encoder(void);~MP4Encoder(void);MP4EncoderResult MP4CreateFile(const char *sFileName,unsigned uRecordTime = DEFAULT_RECORD_TIME);MP4EncoderResult MP4AddH264Track(const uint8_t *sData, int nSize,int nWidth, int nHeight, int nFrameRate = 25);MP4EncoderResult MP4AddAACTrack(const uint8_t *sData, int nSize);MP4EncoderResult MP4WriteH264Data(uint8_t *sData, int nSize, uint64_t u64PTS);MP4EncoderResult MP4WriteAACData(const uint8_t *sData, int nSize,uint64_t u64PTS);void MP4ReleaseFile();
private:unsigned m_uSecond;MP4FileHandle m_hFile;bool m_bFirstAudio, m_bFirstVideo;MP4TrackId m_videoTrack, m_audioTrack;uint64_t m_u64AudioPTS, m_u64VideoPTS, m_u64FirstPTS, m_u64LastPTS;
};

实现部分:

// MP4Encoder.cpp
#pragma once
#include "MP4Encoder.h"
#pragma comment(lib, "../lib/libmp4v2.lib")#define MIN_FRAME_SIZE 32
#define VIDEO_TIME_SCALE 90000
#define AUDIO_TIME_SCALE 8000
#define MOVIE_TIME_SCALE VIDEO_TIME_SCALE
#define PTS2TIME_SCALE(CurPTS, PrevPTS, timeScale) \((MP4Duration)((CurPTS - PrevPTS) * 1.0 / (double)(1e+6) * timeScale))
#define INVALID_PTS 0xFFFFFFFFFFFFFFFF
/* Warning: Followings are magic data originally */
#define DEFAULT_VIDEO_TRACK_NUM 3
#define DEFAULT_VIDEO_PROFILE_LEVEL 1
#define DEFAULT_AUDIO_PROFILE_LEVEL 2MP4Encoder::MP4Encoder(void): m_hFile(MP4_INVALID_FILE_HANDLE), m_bFirstVideo(true), m_bFirstAudio(true), m_uSecond(DEFAULT_RECORD_TIME), m_videoTrack(MP4_INVALID_TRACK_ID), m_audioTrack(MP4_INVALID_TRACK_ID), m_u64VideoPTS(0), m_u64AudioPTS(0), m_u64FirstPTS(INVALID_PTS), m_u64LastPTS(INVALID_PTS)
{
}MP4Encoder::~MP4Encoder(void)
{
}MP4EncoderResult MP4Encoder::MP4CreateFile(const char *sFileName,unsigned uRecordTime /* = DEFAULT_RECORD_TIME */)
{m_hFile = MP4Create(sFileName);if (m_hFile == MP4_INVALID_FILE_HANDLE)return MP4ENCODER_ERROR(MP4ENCODER_E_CREATE_FAIL);if (!MP4SetTimeScale(m_hFile, MOVIE_TIME_SCALE))return MP4ENCODER_ERROR(MP4ENCODER_E_CREATE_FAIL);m_uSecond = uRecordTime;return MP4ENCODER_ENONE;
}MP4EncoderResult MP4Encoder::MP4AddH264Track(const uint8_t *sData, int nSize,int nWidth, int nHeight, int nFrameRate/* = 25 */)
{int sps, pps;for (sps = 0; sps < nSize;)if (sData[sps++] == 0x00 && sData[sps++] == 0x00 && sData[sps++] == 0x00&& sData[sps++] == 0x01)break;for (pps = sps; pps < nSize;)if (sData[pps++] == 0x00 && sData[pps++] == 0x00 && sData[pps++] == 0x00&& sData[pps++] == 0x01)break;if (sps >= nSize || pps >= nSize)return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_VIDEO_TRACK);m_videoTrack = MP4AddH264VideoTrack(m_hFile, VIDEO_TIME_SCALE,VIDEO_TIME_SCALE / nFrameRate, nWidth, nHeight,sData[sps + 1], sData[sps + 2], sData[sps + 3], DEFAULT_VIDEO_TRACK_NUM);if (MP4_INVALID_TRACK_ID == m_videoTrack)return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_VIDEO_TRACK);MP4SetVideoProfileLevel(m_hFile, DEFAULT_VIDEO_PROFILE_LEVEL);MP4AddH264SequenceParameterSet(m_hFile, m_videoTrack, sData + sps,pps - sps - 4);MP4AddH264PictureParameterSet(m_hFile, m_videoTrack, sData + pps,nSize - pps);return MP4ENCODER_ENONE;
}MP4EncoderResult MP4Encoder::MP4AddAACTrack(const uint8_t *sData, int nSize)
{m_audioTrack = MP4AddAudioTrack(m_hFile, AUDIO_TIME_SCALE,/*** In fact, this is not a magic number. A formula might be:* SampleRate * ChannelNum * 2 / SampleFormat* 8000 * 1 * 2 / 16 (字节对齐,这里是 AV_SAMPLE_FMT_S16)*/AUDIO_TIME_SCALE / 8, MP4_MPEG4_AUDIO_TYPE);if (MP4_INVALID_TRACK_ID == m_audioTrack)return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_AUDIO_TRACK);MP4SetAudioProfileLevel(m_hFile, DEFAULT_AUDIO_PROFILE_LEVEL);if (!MP4SetTrackESConfiguration(m_hFile, m_audioTrack, sData, nSize))return MP4ENCODER_ERROR(MP4ENCODER_E_ADD_AUDIO_TRACK);return MP4ENCODER_ENONE;
}MP4EncoderResult MP4Encoder::MP4WriteH264Data(uint8_t *sData, int nSize, uint64_t u64PTS)
{if (nSize < MIN_FRAME_SIZE)return MP4ENCODER_ENONE;bool result = false;sData[0] = (nSize - 4) >> 24;sData[1] = (nSize - 4) >> 16;sData[2] = (nSize - 4) >> 8;sData[3] = nSize - 4;if (m_bFirstVideo){if (m_u64FirstPTS > u64PTS)m_u64FirstPTS = u64PTS;m_u64VideoPTS = u64PTS;m_bFirstVideo = false;}if ((sData[4] & 0x0F) == 5)result = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,PTS2TIME_SCALE(u64PTS, m_u64VideoPTS, VIDEO_TIME_SCALE));elseresult = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,PTS2TIME_SCALE(u64PTS, m_u64VideoPTS, VIDEO_TIME_SCALE), 0, false);if (!result)return MP4ENCODER_ERROR(MP4ENCODER_E_WRITE_VIDEO_DATA);m_u64LastPTS = m_u64VideoPTS = u64PTS;if (m_uSecond && (m_u64LastPTS - m_u64FirstPTS) / (1e+6) >= m_uSecond)return MP4ENCODER_ERROR(MP4ENCODER_WARN_RECORD_OVER);return MP4ENCODER_ENONE;
}MP4EncoderResult MP4Encoder::MP4WriteAACData(const uint8_t *sData, int nSize,uint64_t u64PTS)
{if (nSize < MIN_FRAME_SIZE)return MP4ENCODER_ENONE;bool result = false;if (m_bFirstAudio){if (m_u64FirstPTS > u64PTS)m_u64FirstPTS = u64PTS;m_u64AudioPTS = u64PTS;m_bFirstAudio = false;}result = MP4WriteSample(m_hFile, m_audioTrack, sData, nSize,PTS2TIME_SCALE(u64PTS, m_u64AudioPTS, AUDIO_TIME_SCALE));if (!result)return MP4ENCODER_ERROR(MP4ENCODER_E_WRITE_AUDIO_DATA);m_u64LastPTS = m_u64AudioPTS = u64PTS;if (m_uSecond && (m_u64LastPTS - m_u64FirstPTS) / (1e+6) >= m_uSecond)return MP4ENCODER_ERROR(MP4ENCODER_WARN_RECORD_OVER);return MP4ENCODER_ENONE;
}void MP4Encoder::MP4ReleaseFile()
{if (m_hFile != MP4_INVALID_FILE_HANDLE){MP4Close(m_hFile);m_hFile = MP4_INVALID_FILE_HANDLE;}
}

注意,这个是 PC 版本,Android 版本要修改 MP4WriteSample 中的 render!

但是问题又出现了,官方文档告知我们:如果是写视频数据,那么 MP4WriteSample 中的 render 是是不应该设置为默认值 0 的,因为有可能出现音视频不同步,结果我按照说明传递了参数,在手机上同步的更好,但是文件导出到电脑上播放时就会有音视频不同步的问题,这是不是官方的 bug 呢?期待有人去向官方反馈一下,因此现在我们就设置为默认值了,但是不保证以后是否要更改!

这篇关于MP4V2封装的类库,可将H264和AAC直接打包到MP4容器中,堪称经典的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/760970

相关文章

Spring IoC 容器的使用详解(最新整理)

《SpringIoC容器的使用详解(最新整理)》文章介绍了Spring框架中的应用分层思想与IoC容器原理,通过分层解耦业务逻辑、数据访问等模块,IoC容器利用@Component注解管理Bean... 目录1. 应用分层2. IoC 的介绍3. IoC 容器的使用3.1. bean 的存储3.2. 方法注

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

Python中对FFmpeg封装开发库FFmpy详解

《Python中对FFmpeg封装开发库FFmpy详解》:本文主要介绍Python中对FFmpeg封装开发库FFmpy,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、FFmpy简介与安装1.1 FFmpy概述1.2 安装方法二、FFmpy核心类与方法2.1 FF

Python程序打包exe,单文件和多文件方式

《Python程序打包exe,单文件和多文件方式》:本文主要介绍Python程序打包exe,单文件和多文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python 脚本打成exe文件安装Pyinstaller准备一个ico图标打包方式一(适用于文件较少的程

Maven项目打包时添加本地Jar包的操作步骤

《Maven项目打包时添加本地Jar包的操作步骤》在Maven项目开发中,我们经常会遇到需要引入本地Jar包的场景,比如使用未发布到中央仓库的第三方库或者处理版本冲突的依赖项,本文将详细介绍如何通过M... 目录一、适用场景说明​二、核心操作命令​1. 命令格式解析​2. 实战案例演示​三、项目配置步骤​1

Spring Boot项目打包和运行的操作方法

《SpringBoot项目打包和运行的操作方法》SpringBoot应用内嵌了Web服务器,所以基于SpringBoot开发的web应用也可以独立运行,无须部署到其他Web服务器中,下面以打包dem... 目录一、打包为JAR包并运行1.打包为可执行的 JAR 包2.运行 JAR 包二、打包为WAR包并运行

Python将字库文件打包成可执行文件的常见方法

《Python将字库文件打包成可执行文件的常见方法》在Python打包时,如果你想将字库文件一起打包成一个可执行文件,有几种常见的方法,具体取决于你使用的打包工具,下面就跟随小编一起了解下具体的实现方... 目录使用 PyInstaller基本方法 - 使用 --add-data 参数使用 spec 文件(

SpringIOC容器Bean初始化和销毁回调方式

《SpringIOC容器Bean初始化和销毁回调方式》:本文主要介绍SpringIOC容器Bean初始化和销毁回调方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录前言1.@Bean指定初始化和销毁方法2.实现接口3.使用jsR250总结前言Spring Bea

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求