diff --git a/avidemux/common/ADM_editor/include/ADM_edit.hxx b/avidemux/common/ADM_editor/include/ADM_edit.hxx index 34da68e860..953a9654b9 100644 --- a/avidemux/common/ADM_editor/include/ADM_edit.hxx +++ b/avidemux/common/ADM_editor/include/ADM_edit.hxx @@ -279,6 +279,7 @@ public: uint8_t updateVideoInfo(aviInfo *info); uint32_t getSpecificMpeg4Info( void ); bool getNonClosedGopDelay(uint64_t time,uint32_t *delay); + bool getUserDataUnregistered(uint64_t start,uint8_t *buffer,uint32_t max,uint32_t *length); /************************************ /audioStream ******************************/ bool getAudioStreamsInfo(uint64_t xtime,uint32_t *nbStreams, audioInfo **infos); bool changeAudioStream(uint64_t xtime,uint32_t newstream); diff --git a/avidemux/common/ADM_editor/src/ADM_edVideoCopy.cpp b/avidemux/common/ADM_editor/src/ADM_edVideoCopy.cpp index eed41a0c94..437ad17fb4 100644 --- a/avidemux/common/ADM_editor/src/ADM_edVideoCopy.cpp +++ b/avidemux/common/ADM_editor/src/ADM_edVideoCopy.cpp @@ -965,3 +965,36 @@ bool ADM_Composer::getDirectImageForDebug(uint32_t frameNum,ADMCompressed } return true; } + +/** + \fn getUserDataUnregistered + \brief For H.264, libavcodec parses SEI messages of type user data unregistered to retrieve x264 + version number to apply version-specific quirks. This function copies the NALU containing + this SEI message to buffer allocated by the caller. +*/ +bool ADM_Composer::getUserDataUnregistered(uint64_t start, uint8_t *buffer, uint32_t max, uint32_t *length) +{ + uint32_t segNo; + uint64_t segTime; + + if(false==_segments.convertLinearTimeToSeg(start,&segNo,&segTime)) + return false; + _SEGMENT *seg=_segments.getSegment(segNo); + _VIDEOS *vid=_segments.getRefVideo(seg->_reference); + vidHeader *demuxer=vid->_aviheader; + aviInfo info; + demuxer->getVideoInfo(&info); + if(!isH264Compatible(info.fcc)) + return false; + ADMCompressedImage img; + uint8_t *space=new uint8_t[MAX_FRAME_LENGTH]; + img.data=space; + if(!demuxer->getFrame(0,&img)) + return false; + + bool r=extractH264SEI(img.data,img.dataLength,buffer,max,length); + + delete [] space; + return r; +} +// EOF diff --git a/avidemux/common/ADM_muxerGate/include/ADM_videoCopy.h b/avidemux/common/ADM_muxerGate/include/ADM_videoCopy.h index f46c839ce4..3f544b0ea8 100644 --- a/avidemux/common/ADM_muxerGate/include/ADM_videoCopy.h +++ b/avidemux/common/ADM_muxerGate/include/ADM_videoCopy.h @@ -100,4 +100,19 @@ class ADM_videoStreamCopyAudRemover : public ADM_videoStreamCopy virtual ~ADM_videoStreamCopyAudRemover(); virtual bool getPacket(ADMBitstream *out); }; +/** + \fn ADM_videoStreamCopySeiInjector + \brief If available, inject SEI message containing x264 version into the first access unit of a H.264 stream +*/ +class ADM_videoStreamCopySeiInjector : public ADM_videoStreamCopy +{ +protected: +#define ADM_H264_MAX_SEI_LENGTH 2048 + uint8_t seiBuf[ADM_H264_MAX_SEI_LENGTH]; + uint32_t seiLen; +public: + ADM_videoStreamCopySeiInjector(uint64_t startTime,uint64_t endTime); + virtual ~ADM_videoStreamCopySeiInjector(); + virtual bool getPacket(ADMBitstream *out); +}; #endif diff --git a/avidemux/common/ADM_muxerGate/src/ADM_videoCopySeiInjector.cpp b/avidemux/common/ADM_muxerGate/src/ADM_videoCopySeiInjector.cpp new file mode 100644 index 0000000000..08a29eac0e --- /dev/null +++ b/avidemux/common/ADM_muxerGate/src/ADM_videoCopySeiInjector.cpp @@ -0,0 +1,117 @@ +/** + \file ADM_videoCopySeiInjector + \brief Wrapper + (c) Mean 2008/GPLv2 +*/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "ADM_cpp.h" +#include "ADM_default.h" +#include "ADM_videoCopy.h" +#include "ADM_edit.hxx" +#include "ADM_coreUtils.h" +#include "ADM_videoInfoExtractor.h" +#include "ADM_h264_tag.h" +extern ADM_Composer *video_body; // Fixme! + +/** + * \fn ctor + */ +ADM_videoStreamCopySeiInjector::ADM_videoStreamCopySeiInjector(uint64_t startTime,uint64_t endTime) : ADM_videoStreamCopy(startTime,endTime) +{ + ADM_info("SEI injector created\n"); + uint32_t length=0; + if(video_body->getUserDataUnregistered(startTime, seiBuf, ADM_H264_MAX_SEI_LENGTH, &length)) + { + ADM_info("SEI message with x264 build info present, NAL unit length: %u\n",length); + mixDump(seiBuf,length); + seiLen=length; + }else + { + ADM_info("No x264 build info found, purely passthrough operation.\n"); + seiLen=0; + } +} + +/** + * \fn dtor + */ +ADM_videoStreamCopySeiInjector::~ADM_videoStreamCopySeiInjector() +{ + +} + +/** + * \fn getPacket + */ +bool ADM_videoStreamCopySeiInjector::getPacket(ADMBitstream *out) +{ + if(!ADM_videoStreamCopy::getPacket(out)) + return false; + if(!seiLen) + return true; + if(!(out->flags & AVI_KEY_FRAME)) + return true; + // Check whether this particular SEI message is already present. + uint32_t len=0; + if(extractH264SEI(out->data,out->len,NULL,ADM_H264_MAX_SEI_LENGTH,&len)) + { + if(len!=seiLen) + ADM_warning("SEI message present, but the length is different?\n"); + else + ADM_info("SEI message with x264 version info already present, nothing to do.\n"); + seiLen=0; + return true; + } + // Nope, do we have enough space to inject ours? + if(out->len+seiLen>=out->bufferSize) + { + ADM_warning("Not enough free buffer to inject SEI.\n"); // we'll try with the next keyframe + return true; + } + // Inject SEI from seiBuf + uint8_t *tail=out->data; + uint8_t *head=tail+out->len; + uint8_t stream; + uint32_t nalSize=3; + + while(tail + nalSize < head) + { + nalSize=4; + uint32_t length=(tail[0]<<16)+(tail[1]<<8)+tail[2]; + if((length<<8)+tail[3] > out->len) + nalSize=3; + else + length=(length<<8)+tail[3]; + stream=*(tail+nalSize)&0x1F; + switch(stream) + { + case NAL_SEI: + case NAL_IDR: + case NAL_NON_IDR: + { + memmove(tail+seiLen,tail,head-tail); + memcpy(tail,seiBuf,seiLen); + out->len+=seiLen; + seiLen=0; + ADM_info("SEI message with x264 version info injected.\n"); + return true; + } + default: + tail+=nalSize+length; + continue; + } + } + + return true; +} + +// EOF + diff --git a/avidemux/common/ADM_muxerGate/src/CMakeLists.txt b/avidemux/common/ADM_muxerGate/src/CMakeLists.txt index c0b0377abf..dd1f41adec 100644 --- a/avidemux/common/ADM_muxerGate/src/CMakeLists.txt +++ b/avidemux/common/ADM_muxerGate/src/CMakeLists.txt @@ -3,6 +3,7 @@ ADM_videoCopy.cpp ADM_videoProcess.cpp ADM_videoCopyFromAnnexB.cpp ADM_videoCopyAudRemover.cpp +ADM_videoCopySeiInjector.cpp ) include_directories(../include) ADD_LIBRARY(ADM_muxerGate6 STATIC ${ADM_muxerGate_SRCS}) diff --git a/avidemux/common/gui_savenew.cpp b/avidemux/common/gui_savenew.cpp index 503d008b12..c85e081f53 100644 --- a/avidemux/common/gui_savenew.cpp +++ b/avidemux/common/gui_savenew.cpp @@ -289,6 +289,7 @@ ADM_videoStreamCopy *admSaver::dealWithH26x(bool isAnnexB) default: case 0: // Both source and target are mp4 , nothing to do ADM_info("Input and output are mp4 style, nothing to do\n"); + copy=new ADM_videoStreamCopySeiInjector(markerA,markerB); break; case 1: // source is mp4, target is annexB ADM_info("Input is probably MP4 bitstream, target is annexB\n"); diff --git a/avidemux_core/ADM_coreUtils/include/ADM_videoInfoExtractor.h b/avidemux_core/ADM_coreUtils/include/ADM_videoInfoExtractor.h index b6d3c4a5b6..26ec084729 100644 --- a/avidemux_core/ADM_coreUtils/include/ADM_videoInfoExtractor.h +++ b/avidemux_core/ADM_coreUtils/include/ADM_videoInfoExtractor.h @@ -72,6 +72,8 @@ ADM_COREUTILS6_EXPORT bool extractSPSInfo_mp4Header(uint8_t *data, uint32_t l ADM_COREUTILS6_EXPORT uint8_t extractH264FrameType(uint8_t *buffer,uint32_t len,uint32_t *flags,int *pocLsb,ADM_SPSInfo *sps,uint32_t *recovery=NULL); ADM_COREUTILS6_EXPORT uint8_t extractH265FrameType(uint32_t nalSize,uint8_t *buffer,uint32_t len,uint32_t *flags); ADM_COREUTILS6_EXPORT uint8_t extractH264FrameType_startCode(uint8_t *buffer,uint32_t len,uint32_t *flags,int *pocLsb,ADM_SPSInfo *sps,uint32_t *recovery=NULL); +ADM_COREUTILS6_EXPORT bool extractH264SEI(uint8_t *src, uint32_t inlen, uint8_t *dest, uint32_t bufsize, uint32_t *outlen); // dest may be NULL + ADM_COREUTILS6_EXPORT bool ADM_getH264SpsPpsFromExtraData(uint32_t extraLen,uint8_t *extra, uint32_t *spsLen,uint8_t **spsData, uint32_t *ppsLen,uint8_t **ppsData); // return a copy of pps/sps extracted diff --git a/avidemux_core/ADM_coreUtils/src/ADM_infoExtractorH264.cpp b/avidemux_core/ADM_coreUtils/src/ADM_infoExtractorH264.cpp index 1d22eec20c..d5f64041bd 100644 --- a/avidemux_core/ADM_coreUtils/src/ADM_infoExtractorH264.cpp +++ b/avidemux_core/ADM_coreUtils/src/ADM_infoExtractorH264.cpp @@ -458,27 +458,37 @@ uint8_t extractSPSInfo_internal (uint8_t * data, uint32_t len, ADM_SPSInfo *spsi } return 1; } + /** - \fn getRecoveryFromSei - \brief We dont unescape here, very unlikely needed as we only decode recovery which is small + \fn getInfoFromSei + \brief Get SEI type, decode recovery point + \return 0: failure, 1: recovery, 2: unregistered user data, 3: both */ -static bool getRecoveryFromSei(uint32_t nalSize, uint8_t *org,uint32_t *recoveryLength) +enum { + ADM_H264_SEI_TYPE_OTHER = 0, + ADM_H264_SEI_TYPE_USER_DATA_UNREGISTERED = 1, + ADM_H264_SEI_TYPE_RECOVERY_POINT = 2 +}; + +static int getInfoFromSei(uint32_t nalSize, uint8_t *org, uint32_t *recoveryLength, uint32_t *unregistered) { int originalNalSize=nalSize+16; uint8_t *payloadBuffer=(uint8_t *)malloc(originalNalSize+AV_INPUT_BUFFER_PADDING_SIZE); memset(payloadBuffer,0,originalNalSize+AV_INPUT_BUFFER_PADDING_SIZE); uint8_t *payload=payloadBuffer; - bool r=false; + int r=ADM_H264_SEI_TYPE_OTHER; nalSize=ADM_unescapeH264(nalSize,org,payload); if(nalSize>originalNalSize) { ADM_warning("NAL is way too big : %d, while we expected %d at most\n",nalSize,originalNalSize); free(payloadBuffer); - return false; + return r; } uint8_t *tail=payload+nalSize; *recoveryLength=16; + *unregistered=0; + while( payloadtail) break; switch(sei_type) { + case 5: // Unregistered user data + { + if(sei_size<16) + { + ADM_info("User data too short: %u\n",sei_size); + break; + } + char *udata=(char *)malloc(16+sei_size+1); + getBits bits(sei_size,payload); + for(uint32_t i=0; ilen) nalSize=3; } uint32_t recovery=0xff; + uint32_t unregistered=0; int p=-1; *flags=0; @@ -626,10 +661,10 @@ uint8_t extractH264FrameType(uint8_t *buffer, uint32_t len, uint32_t *flags, int { case NAL_SEI: { - bool sei=getRecoveryFromSei(length-1, head+1,&recovery); + int sei=getInfoFromSei(length-1, head+1, &recovery, &unregistered); if(extRecovery) { - if(sei) + if(sei & ADM_H264_SEI_TYPE_RECOVERY_POINT) *extRecovery=recovery; else recovery=*extRecovery; @@ -681,6 +716,7 @@ uint8_t extractH264FrameType_startCode(uint8_t *buffer, uint32_t len, uint32_t * uint8_t stream; uint32_t hnt=0xffffffff; uint32_t recovery=0xff; + uint32_t unregistered=0; int counter = 0, length = 0; int p = -1; @@ -708,10 +744,10 @@ uint8_t extractH264FrameType_startCode(uint8_t *buffer, uint32_t len, uint32_t * { case NAL_SEI: { - bool sei=getRecoveryFromSei(length, head, &recovery); + int sei=getInfoFromSei(length, head, &recovery, &unregistered); if(extRecovery) { - if(sei) + if(sei & ADM_H264_SEI_TYPE_RECOVERY_POINT) *extRecovery=recovery; else recovery=*extRecovery; @@ -746,6 +782,61 @@ uint8_t extractH264FrameType_startCode(uint8_t *buffer, uint32_t len, uint32_t * return 0; } +/** + * \fn extractH264SEI + * \brief If present, copy SEI containing x264 version info from access unit src to dest + */ +bool extractH264SEI(uint8_t *src, uint32_t inlen, uint8_t *dest, uint32_t bufsize, uint32_t *outlen) +{ + uint8_t *tail = src, *head = src + inlen; + uint8_t stream; + + uint32_t nalSize = 4; +// Check for short nalSize, i.e. size coded on 3 bytes + { + uint32_t length = (tail[0] << 24) + (tail[1] << 16) + (tail[2] << 8) + tail[3]; + if(length > inlen) nalSize = 3; + } + uint32_t unregistered = 0; + + while(tail + nalSize < head) + { + uint32_t length = (tail[0] << 16) + (tail[1] << 8) + (tail[2] << 0); + if(nalSize == 4) + length = (length << 8) + tail[3]; + if(length > inlen) + { + ADM_warning ("Incomplete NALU, length: %u, available: %u\n", length, inlen); + return false; + } + tail += nalSize; + stream = *(tail) & 0x1f; + + if(stream == NAL_SEI) + { + uint32_t recovery=0xff; + if(getInfoFromSei(length-1,tail+1,&recovery,&unregistered) & ADM_H264_SEI_TYPE_USER_DATA_UNREGISTERED) + { + uint32_t l = nalSize + length; + if(l > bufsize) + { + ADM_warning("Insufficient destination buffer, need %u, got %u\n",l,bufsize); + return false; + } + if(dest) + memcpy(dest,tail-nalSize,l); // what about emulation prevention bytes?? + if(outlen) + *outlen = l; + return true; + } + } + tail += length; + } + + return false; +} + + /** \fn extractSPSInfo_mp4Header