ZLMediaKit/src/MediaFile/Mp4Maker.cpp

456 lines
14 KiB
C++
Raw Normal View History

2017-10-09 22:11:01 +08:00
/*
2017-09-27 16:20:30 +08:00
* MIT License
*
2019-05-08 15:40:07 +08:00
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
2017-09-27 16:20:30 +08:00
*
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
2017-04-01 16:35:56 +08:00
#ifdef ENABLE_MP4RECORD
2019-07-26 09:10:27 +08:00
#include <ctime>
2017-04-01 16:35:56 +08:00
#include <sys/stat.h>
2017-05-02 17:15:12 +08:00
#include "Common/config.h"
2017-04-01 16:35:56 +08:00
#include "Mp4Maker.h"
2017-04-25 11:35:41 +08:00
#include "MediaRecorder.h"
#include "Util/File.h"
#include "Util/mini.h"
2018-05-22 11:41:20 +08:00
#include "Util/util.h"
2017-04-01 16:35:56 +08:00
#include "Util/NoticeCenter.h"
2018-10-30 14:59:42 +08:00
#include "Extension/H264.h"
2019-07-30 19:26:38 +08:00
#include "Extension/H265.h"
2018-10-30 14:59:42 +08:00
#include "Extension/AAC.h"
2019-04-03 11:09:50 +08:00
#include "Thread/WorkThreadPool.h"
2018-10-30 14:59:42 +08:00
2019-07-30 19:26:38 +08:00
#include "mov-buffer.h"
#include "mov-format.h"
using namespace toolkit;
namespace mediakit {
2019-07-30 19:26:38 +08:00
#if defined(_WIN32) || defined(_WIN64)
#define fseek64 _fseeki64
#define ftell64 _ftelli64
#else
#define fseek64 fseek
#define ftell64 ftell
#endif
static int movfileRead(void* fp, void* data, uint64_t bytes)
2019-07-30 19:26:38 +08:00
{
if (bytes == fread(data, 1, bytes, (FILE*)fp))
return 0;
return 0 != ferror((FILE*)fp) ? ferror((FILE*)fp) : -1 /*EOF*/;
}
static int movfileWrite(void* fp, const void* data, uint64_t bytes)
2019-07-30 19:26:38 +08:00
{
return bytes == fwrite(data, 1, bytes, (FILE*)fp) ? 0 : ferror((FILE*)fp);
}
static int movfileSeek(void* fp, uint64_t offset)
2019-07-30 19:26:38 +08:00
{
return fseek64((FILE*)fp, offset, SEEK_SET);
}
static uint64_t movfileTell(void* fp)
2019-07-30 19:26:38 +08:00
{
return ftell64((FILE*)fp);
}
const struct mov_buffer_t* movfileBuffer(void)
2019-07-30 19:26:38 +08:00
{
static struct mov_buffer_t s_io = {
movfileRead,
movfileWrite,
movfileSeek,
movfileTell,
2019-07-30 19:26:38 +08:00
};
return &s_io;
}
2017-04-01 16:35:56 +08:00
string timeStr(const char *fmt) {
std::tm tm_snapshot;
auto time = ::time(NULL);
2018-05-22 11:41:20 +08:00
#if defined(_WIN32)
localtime_s(&tm_snapshot, &time); // thread-safe
2017-04-01 16:35:56 +08:00
#else
localtime_r(&time, &tm_snapshot); // POSIX
2018-05-22 11:41:20 +08:00
#endif
2017-04-01 16:35:56 +08:00
const size_t size = 1024;
char buffer[size];
auto success = std::strftime(buffer, size, fmt, &tm_snapshot);
if (0 == success)
return string(fmt);
return buffer;
}
Mp4Maker::Mp4Maker(const string& strPath,
const string &strVhost,
const string &strApp,
2018-10-28 00:15:27 +08:00
const string &strStreamId) {
2017-04-01 16:35:56 +08:00
DebugL << strPath;
2018-10-24 15:43:52 +08:00
_strPath = strPath;
2017-04-01 16:35:56 +08:00
/////record 业务逻辑//////
2018-10-24 15:43:52 +08:00
_info.strAppName = strApp;
_info.strStreamId = strStreamId;
_info.strVhost = strVhost;
_info.strFolder = strPath;
memset(&_movH265info, 0, sizeof(_movH265info));
_movH265info.videoTrack = -1;
_movH265info.audioTrack = -1;
2017-04-01 16:35:56 +08:00
//----record 业务逻辑----//
}
Mp4Maker::~Mp4Maker() {
closeFile();
}
2018-10-28 00:15:27 +08:00
void Mp4Maker::inputH264(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
auto iType = H264_TYPE(((uint8_t*)pData)[4]);
if (H264Frame::NAL_B_P <= iType && iType <= H264Frame::NAL_IDR){
2018-10-28 00:15:27 +08:00
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
iTimeInc = MAX(0,MIN(iTimeInc,500));
if( (iTimeInc == 0 || iTimeInc == 500) && H264Frame::NAL_IDR != iType){
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
}
if ( _strLastVideo.size() ){
//如果出现SPS PPS黏连的帧,那么在这里先处理
inputH264_l((char *) _strLastVideo.data(), _strLastVideo.size(), _ui32LastVideoTime);
_strLastVideo = "";
}
inputH264_l((char *) pData, ui32Length, ui32TimeStamp);
_ui32LastVideoTime = ui32TimeStamp;
}else{
//SPS PPS 进入等待组合为一帧
2018-10-28 00:15:27 +08:00
_strLastVideo.append((char *)pData,ui32Length);
2018-10-24 15:43:52 +08:00
_ui32LastVideoTime = ui32TimeStamp;
2017-04-01 16:35:56 +08:00
}
}
2019-07-30 19:26:38 +08:00
void Mp4Maker::inputH265(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
2019-07-30 19:26:38 +08:00
auto iType = H265_TYPE(((uint8_t*)pData)[4]);
if (iType <= H265Frame::NAL_IDR_W_RADL ){
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastVideoTime;
iTimeInc = MAX(0,MIN(iTimeInc,500));
if((iTimeInc == 0 || iTimeInc == 500) && H265Frame::NAL_IDR_W_RADL != iType){
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastVideoTime;
}
if ( _strLastVideo.size() ){
//如果出现SPS PPS VPS黏连的帧,那么在这里先处理
inputH265_l((char *) _strLastVideo.data(), _strLastVideo.size(), _ui32LastVideoTime);
2019-07-30 19:26:38 +08:00
_strLastVideo = "";
}
inputH265_l((char *) pData, ui32Length, ui32TimeStamp);
_ui32LastVideoTime = ui32TimeStamp;
2019-07-30 19:26:38 +08:00
}else{
_strLastVideo.append((char *)pData,ui32Length);
_ui32LastVideoTime = ui32TimeStamp;
}
}
void Mp4Maker::inputAAC(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp){
int64_t iTimeInc = (int64_t)ui32TimeStamp - (int64_t)_ui32LastAudioTime;
iTimeInc = MAX(0,MIN(iTimeInc,500));
if(iTimeInc == 0 || iTimeInc == 500){
WarnL << "abnormal time stamp increment:" << ui32TimeStamp << " " << _ui32LastAudioTime;
}
inputAAC_l((char *) pData, ui32Length, ui32TimeStamp);
_ui32LastAudioTime = ui32TimeStamp;
2017-04-01 16:35:56 +08:00
}
void Mp4Maker::inputH264_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
2019-05-28 17:14:36 +08:00
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
2018-10-30 15:56:00 +08:00
auto iType = H264_TYPE(((uint8_t*)pData)[4]);
int32_t compositionTime;
if( iType >= H264Frame::NAL_IDR && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)){
2017-04-01 16:35:56 +08:00
//在I帧率处新建MP4文件
//如果文件未创建或者文件超过10分钟则创建新文件
//每一个录制的MP4文件时间戳都要从0开始
_startPts = ui32TimeStamp;
createFile();
2017-04-01 16:35:56 +08:00
}
char *pNualData = (char *)pData;
if (_movH265info.pMov!=NULL){
int vcl;
if (_movH265info.videoTrack < 0){
//解析解析SPS PPS,未添加track的时候执行
int n = h264_annexbtomp4(&_movH265info.avc, pData, ui32Length, _sBbuffer, sizeof(_sBbuffer), &vcl);
if (_movH265info.avc.nb_sps < 1 || _movH265info.avc.nb_pps < 1){
return; // waiting for sps/pps
}
uint8_t sExtraData[64 * 1024];
int extraDataSize = mpeg4_avc_decoder_configuration_record_save(&_movH265info.avc, sExtraData, sizeof(sExtraData));
if (extraDataSize <= 0){
// invalid HVCC
return;
}
// TODO: waiting for key frame ???
_movH265info.videoTrack = mov_writer_add_video(_movH265info.pMov,
MOV_OBJECT_H264,
_movH265info.width,
_movH265info.height,
sExtraData,
extraDataSize);
return;
}
if ( iType <= H264Frame::NAL_IDR ){
uint8_t *ptr = (uint8_t*)pData;
ptr[0] = (uint8_t)((ui32Length-4 >> 24) & 0xFF);
ptr[1] = (uint8_t)((ui32Length-4 >> 16) & 0xFF);
ptr[2] = (uint8_t)((ui32Length-4 >> 8) & 0xFF);
ptr[3] = (uint8_t)((ui32Length-4 >> 0) & 0xFF);
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
mov_writer_write(_movH265info.pMov,
_movH265info.videoTrack, pData, ui32Length,
ui32Pts,
ui32Pts,
iType == H264Frame::NAL_IDR ? MOV_AV_FLAG_KEYFREAME : 0);
}
2017-04-01 16:35:56 +08:00
}
2017-04-01 16:35:56 +08:00
}
2019-07-30 19:26:38 +08:00
void Mp4Maker::inputH265_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
2019-05-28 17:14:36 +08:00
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
2019-07-30 19:26:38 +08:00
int32_t compositionTime;
auto iType = H265_TYPE(((uint8_t*)pData)[4]);
if( iType >= H265Frame::NAL_IDR_W_RADL && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)){
_h265Record = 1;
//每一个录制的MP4文件时间戳都要从0开始
_startPts = ui32TimeStamp;
//在I帧率处新建MP4文件
//如果文件未创建或者文件超过最长时间则创建新文件
2018-10-28 00:15:27 +08:00
createFile();
}
2019-07-30 19:26:38 +08:00
char *pNualData = (char *)pData;
if (_movH265info.pMov!=NULL){
2019-07-30 19:26:38 +08:00
int vcl;
if (_movH265info.videoTrack < 0){
//解析解析VPS SPS PPS,未添加track的时候执行
int n = h265_annexbtomp4(&_movH265info.hevc, pData, ui32Length, _sBbuffer, sizeof(_sBbuffer), &vcl);
2019-07-30 19:26:38 +08:00
if (_movH265info.hevc.numOfArrays < 1){
return; // waiting for vps/sps/pps
}
uint8_t sExtraData[64 * 1024];
int extraDataSize = mpeg4_hevc_decoder_configuration_record_save(&_movH265info.hevc, sExtraData, sizeof(sExtraData));
if (extraDataSize <= 0){
// invalid HVCC
return;
}
// TODO: waiting for key frame ???
_movH265info.videoTrack = mov_writer_add_video(_movH265info.pMov, MOV_OBJECT_HEVC, _movH265info.width, _movH265info.height, sExtraData, extraDataSize);
return;
}
if ( iType <= H265Frame::NAL_IDR_W_RADL )
{
uint8_t *ptr = (uint8_t*)pData;
ptr[0] = (uint8_t)((ui32Length-4 >> 24) & 0xFF);
ptr[1] = (uint8_t)((ui32Length-4 >> 16) & 0xFF);
ptr[2] = (uint8_t)((ui32Length-4 >> 8) & 0xFF);
ptr[3] = (uint8_t)((ui32Length-4 >> 0) & 0xFF);
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
mov_writer_write(_movH265info.pMov,
_movH265info.videoTrack,
pData,
ui32Length,
ui32Pts,
ui32Pts,
iType == H265Frame::NAL_IDR_W_RADL ? MOV_AV_FLAG_KEYFREAME : 0);
2019-07-30 19:26:38 +08:00
}
}
}
void Mp4Maker::inputAAC_l(void *pData, uint32_t ui32Length, uint32_t ui32TimeStamp) {
2019-07-30 19:26:38 +08:00
GET_CONFIG(uint32_t,recordSec,Record::kFileSecond);
if (!_haveVideo && (_movH265info.pMov == NULL || _ticker.elapsedTime() > recordSec * 1000)) {
_startPts = ui32TimeStamp;
createFile();
}
2019-07-30 19:26:38 +08:00
if (-1 != _movH265info.audioTrack && _movH265info.pMov != NULL){
uint32_t ui32Pts = ui32TimeStamp < _movH265info.startPts ? 0 : ui32TimeStamp-_movH265info.startPts;
mov_writer_write(_movH265info.pMov, _movH265info.audioTrack, (uint8_t*)pData, ui32Length, ui32Pts, ui32Pts, 0);
2018-10-28 00:15:27 +08:00
}
2017-04-01 16:35:56 +08:00
}
void Mp4Maker::createFile() {
closeFile();
auto strDate = timeStr("%Y-%m-%d");
auto strTime = timeStr("%H-%M-%S");
2018-10-24 15:43:52 +08:00
auto strFileTmp = _strPath + strDate + "/." + strTime + ".mp4";
auto strFile = _strPath + strDate + "/" + strTime + ".mp4";
2017-04-01 16:35:56 +08:00
/////record 业务逻辑//////
2018-10-24 15:43:52 +08:00
_info.ui64StartedTime = ::time(NULL);
_info.strFileName = strTime + ".mp4";
_info.strFilePath = strFile;
2018-02-09 11:42:55 +08:00
2019-05-28 17:14:36 +08:00
GET_CONFIG(string,appName,Record::kAppName);
2019-05-29 09:24:02 +08:00
_info.strUrl = appName + "/"
+ _info.strAppName + "/"
+ _info.strStreamId + "/"
+ strDate + "/"
+ strTime + ".mp4";
2018-02-09 11:42:55 +08:00
2017-04-01 16:35:56 +08:00
//----record 业务逻辑----//
2018-05-22 11:41:20 +08:00
#if !defined(_WIN32)
2017-04-01 16:35:56 +08:00
File::createfile_path(strFileTmp.data(), S_IRWXO | S_IRWXG | S_IRWXU);
2018-05-22 11:41:20 +08:00
#else
File::createfile_path(strFileTmp.data(), 0);
#endif
2019-07-30 19:26:38 +08:00
memset(&_movH265info, 0, sizeof(_movH265info));
_movH265info.videoTrack = -1;
_movH265info.audioTrack = -1;
_movH265info.width = 0;
_movH265info.height = 0;
_movH265info.pFile = fopen(strFileTmp.data(), "wb+");
_movH265info.startPts = _startPts;
_movH265info.pMov = mov_writer_create(movfileBuffer(), _movH265info.pFile, 0/*MOV_FLAG_FASTSTART*/);
2019-07-30 19:26:38 +08:00
2018-10-24 15:43:52 +08:00
_strFileTmp = strFileTmp;
_strFile = strFile;
_ticker.resetTime();
2018-10-23 16:41:25 +08:00
2019-07-30 19:26:38 +08:00
if ( _h265Record ){
auto videoTrack = dynamic_pointer_cast<H265Track>(getTrack(TrackVideo));
if(videoTrack){
_movH265info.width = videoTrack->getVideoWidth();
_movH265info.height = videoTrack->getVideoHeight();
2018-10-28 00:15:27 +08:00
}
}else{
2019-07-30 19:26:38 +08:00
auto videoTrack = dynamic_pointer_cast<H264Track>(getTrack(TrackVideo));
if(videoTrack){
// auto &sps = videoTrack->getSps();
// auto &pps = videoTrack->getPps();
_movH265info.width = videoTrack->getVideoWidth();
_movH265info.height = videoTrack->getVideoHeight();
}
2018-10-28 00:15:27 +08:00
}
if ( _movH265info.width <=0 || _movH265info.height <= 0 )
WarnL << "分辨率获取失败,MP4录制异常";
2018-10-28 00:15:27 +08:00
auto audioTrack = dynamic_pointer_cast<AACTrack>(getTrack(TrackAudio));
if(audioTrack){
_audioSampleRate = audioTrack->getAudioSampleRate();
2019-07-30 19:26:38 +08:00
_audioChannel = audioTrack->getAudioChannel();
uint8_t extra_data[64 * 1024];
_movH265info.audioTrack = mov_writer_add_audio(_movH265info.pMov, MOV_OBJECT_AAC, _audioChannel, 16, _audioSampleRate, audioTrack->getAacCfg().data(), 2);
if (-1 == _movH265info.audioTrack)
WarnL << "添加音频通道失败:" << strFileTmp;
2018-10-28 00:15:27 +08:00
}
2017-04-01 16:35:56 +08:00
}
2019-04-03 11:09:50 +08:00
void Mp4Maker::asyncClose() {
auto strFileTmp = _strFileTmp;
auto strFile = _strFile;
auto info = _info;
2019-07-30 19:26:38 +08:00
FILE *pFile = _movH265info.pFile;
mov_writer_t* hMp4 = _movH265info.pMov;
WorkThreadPool::Instance().getExecutor()->async([hMp4,strFileTmp,strFile,info,pFile]() {
2019-04-03 11:09:50 +08:00
//获取文件录制时间放在MP4Close之前是为了忽略MP4Close执行时间
const_cast<Mp4Info&>(info).ui64TimeLen = ::time(NULL) - info.ui64StartedTime;
//MP4Close非常耗时所以要放在后台线程执行
if (hMp4 != NULL ){
mov_writer_destroy(hMp4);
2019-07-30 19:26:38 +08:00
fclose(pFile);
DebugL << "fclose end";
2019-07-30 19:26:38 +08:00
}
2019-04-03 11:09:50 +08:00
//临时文件名改成正式文件名防止mp4未完成时被访问
rename(strFileTmp.data(),strFile.data());
//获取文件大小
struct stat fileData;
stat(strFile.data(), &fileData);
const_cast<Mp4Info&>(info).ui64FileSize = fileData.st_size;
/////record 业务逻辑//////
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastRecordMP4,info);
});
}
2017-04-01 16:35:56 +08:00
void Mp4Maker::closeFile() {
if (_movH265info.pMov != NULL) {
asyncClose();
memset(&_movH265info, 0, sizeof(_movH265info));
_movH265info.pMov = NULL;
_movH265info.videoTrack = -1;
_movH265info.audioTrack = -1;
}
2017-04-01 16:35:56 +08:00
}
2018-10-28 00:15:27 +08:00
void Mp4Maker::onTrackFrame(const Frame::Ptr &frame) {
switch (frame->getCodecId()){
case CodecH264:{
//需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息
inputH264(frame->data() , frame->size(),frame->pts());
2018-10-28 00:15:27 +08:00
}
break;
case CodecAAC:{
//不需要ADTS头
inputAAC(frame->data() + frame->prefixSize(), frame->size() - frame->prefixSize(),frame->pts());
2018-10-28 00:15:27 +08:00
}
break;
2019-07-30 19:26:38 +08:00
case CodecH265:{
//需要带00 00 00 01,方便在mov_writer_write的时候字节修改这4Byte为长度信息
inputH265(frame->data() , frame->size(),frame->pts());
2019-07-30 19:26:38 +08:00
}
break;
2018-10-28 00:15:27 +08:00
default:
break;
}
}
void Mp4Maker::onAllTrackReady() {
_haveVideo = getTrack(TrackVideo).operator bool();
}
2018-10-24 17:17:55 +08:00
} /* namespace mediakit */
2017-04-01 16:35:56 +08:00
#endif //ENABLE_MP4RECORD