Skip to content

Commit

Permalink
[coreUtils/editor/muxerGate] Inject SEI message with x264 build info …
Browse files Browse the repository at this point in the history
…into the first access unit when in copy mode and saving not from the start of ref video, AVC type H.264 streams only for now
  • Loading branch information
eumagga0x2a committed Oct 2, 2019
1 parent 7eb6ae3 commit db235b6
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 11 deletions.
1 change: 1 addition & 0 deletions avidemux/common/ADM_editor/include/ADM_edit.hxx
Expand Up @@ -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);
Expand Down
33 changes: 33 additions & 0 deletions avidemux/common/ADM_editor/src/ADM_edVideoCopy.cpp
Expand Up @@ -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
15 changes: 15 additions & 0 deletions avidemux/common/ADM_muxerGate/include/ADM_videoCopy.h
Expand Up @@ -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
117 changes: 117 additions & 0 deletions 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

1 change: 1 addition & 0 deletions avidemux/common/ADM_muxerGate/src/CMakeLists.txt
Expand Up @@ -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})
1 change: 1 addition & 0 deletions avidemux/common/gui_savenew.cpp
Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions avidemux_core/ADM_coreUtils/include/ADM_videoInfoExtractor.h
Expand Up @@ -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
Expand Down
113 changes: 102 additions & 11 deletions avidemux_core/ADM_coreUtils/src/ADM_infoExtractorH264.cpp
Expand Up @@ -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( payload<tail)
{
uint32_t sei_type=0,sei_size=0;
Expand Down Expand Up @@ -510,14 +520,38 @@ static bool getRecoveryFromSei(uint32_t nalSize, uint8_t *org,uint32_t *recovery
if(payload+sei_size>tail) 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; i<sei_size; i++)
udata[i]=bits.get(8);
udata[sei_size]=0;
int build;
if(1!=sscanf(udata+16,"x264 - core %d",&build))
{
ADM_info("Unregistered user data doesn't match the one expected for x264\n");
mixDump((uint8_t *)udata,sei_size);
break;
}
free(udata);
*unregistered=sei_size;
ADM_info("Found unregistered user data from x264 build %d, size: %u\n",build,sei_size);
r |= ADM_H264_SEI_TYPE_USER_DATA_UNREGISTERED;
break;
}
case 6: // Recovery point
{
getBits bits(sei_size,payload);
int distance=bits.getUEG();
seiprintf("Recovery distance: %d\n",distance);
*recoveryLength=distance;
r=true;
goto abtSei;
r |= ADM_H264_SEI_TYPE_RECOVERY_POINT;
break;
}
default:
Expand Down Expand Up @@ -603,6 +637,7 @@ uint8_t extractH264FrameType(uint8_t *buffer, uint32_t len, uint32_t *flags, int
if(length>len) nalSize=3;
}
uint32_t recovery=0xff;
uint32_t unregistered=0;
int p=-1;

*flags=0;
Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit db235b6

Please sign in to comment.