From 1def679201f164ae8af85797f3038b5469be2ca8 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Thu, 22 Oct 2020 14:44:13 -0700 Subject: [PATCH 01/19] removing mp4reader dependency from GetGPMFSampleRate() --- demo/GPMF_demo.c | 18 +- demo/GPMF_mp4reader.c | 390 ++++-------------------------------------- demo/GPMF_mp4reader.h | 20 +-- demo/GPMF_utils.c | 352 ++++++++++++++++++++++++++++++++++++++ demo/GPMF_utils.h | 49 ++++++ 5 files changed, 458 insertions(+), 371 deletions(-) create mode 100644 demo/GPMF_utils.c create mode 100644 demo/GPMF_utils.h diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index 5c66193..8ab23fe 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -26,6 +26,7 @@ #include "../GPMF_parser.h" #include "GPMF_mp4reader.h" +#include "GPMF_utils.h" #define SHOW_VIDEO_FRAMERATE 1 #define SHOW_PAYLOAD_TIME 1 @@ -115,9 +116,6 @@ int main(int argc, char* argv[]) metadatalength = GetDuration(mp4); - //If the GPMF streams are using (non-zero) timestamps, which stream should time zero be relative to. - //SetTimeBaseStream(mp4, STR2FOURCC("SHUT")); - if (metadatalength > 0.0) { uint32_t index, payloads = GetNumberPayloads(mp4); @@ -137,7 +135,7 @@ int main(int argc, char* argv[]) { double in = 0.0, out = 0.0; //times payloadsize = GetPayloadSize(mp4, index); - payload = GetPayload(mp4, payload, index); + payload = GetPayload(mp4, index); if (payload == NULL) goto cleanup; @@ -388,6 +386,13 @@ int main(int argc, char* argv[]) if (show_computed_samplerates) { + mp4callbacks cbobject; + cbobject.mp4handle = mp4; + cbobject.cbGetNumberPayloads = GetNumberPayloads; + cbobject.cbGetPayload = GetPayload; + cbobject.cbGetPayloadSize = GetPayloadSize; + cbobject.cbGetEditListOffsetRationalTime = GetEditListOffsetRationalTime; + printf("COMPUTED SAMPLERATES:\n"); // Find all the available Streams and compute they sample rates while (GPMF_OK == GPMF_FindNext(ms, GPMF_KEY_STREAM, GPMF_RECURSE_LEVELS|GPMF_TOLERANT)) @@ -396,7 +401,9 @@ int main(int argc, char* argv[]) { double start, end; uint32_t fourcc = GPMF_Key(ms); - double rate = GetGPMFSampleRate(mp4, fourcc, GPMF_SAMPLE_RATE_PRECISE, &start, &end);// GPMF_SAMPLE_RATE_FAST); + + + double rate = GetGPMFSampleRate(cbobject, fourcc, 0, GPMF_SAMPLE_RATE_PRECISE, &start, &end);// GPMF_SAMPLE_RATE_FAST); printf(" %c%c%c%c sampling rate = %fHz (time %f to %f)\",\n", PRINTF_4CC(fourcc), rate, start, end); } } @@ -404,7 +411,6 @@ int main(int argc, char* argv[]) cleanup: if (ms) GPMF_Free(ms); - if (payload) FreePayload(payload); payload = NULL; CloseSource(mp4); } diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index d7ce75b..71bfc42 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -2,7 +2,7 @@ * * @brief Way Too Crude MP4|MOV reader * -* @version 1.8.3 +* @version 2.0.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -29,7 +29,6 @@ #include #include "GPMF_mp4reader.h" -#include "../GPMF_common.h" #define PRINT_MP4_STRUCTURE 0 @@ -52,33 +51,40 @@ uint32_t GetNumberPayloads(size_t handle) return 0; } -uint32_t *GetPayload(size_t handle, uint32_t *lastpayload, uint32_t index) +uint32_t *GetPayload(size_t handle, uint32_t index) { mp4object *mp4 = (mp4object *)handle; if (mp4 == NULL) return NULL; - uint32_t *MP4buffer = NULL; if (index < mp4->indexcount && mp4->mediafp) { if ((mp4->filesize >= mp4->metaoffsets[index]+mp4->metasizes[index]) && (mp4->metasizes[index] > 0)) { - MP4buffer = (uint32_t *)realloc((void *)lastpayload, mp4->metasizes[index]); + uint32_t buffsizeneeded = mp4->metasizes[index] + 256; // Add a little more to limit reallocations + if (mp4->payloadBuffer == NULL) + { + mp4->payloadBuffer = (uint32_t*)malloc(buffsizeneeded); + mp4->payloadBufferSize = buffsizeneeded; + } + else if (mp4->payloadBufferSize < mp4->metasizes[index]) + { + mp4->payloadBuffer = (uint32_t*)realloc((void*)mp4->payloadBuffer, buffsizeneeded); + mp4->payloadBufferSize = buffsizeneeded; + } - if (MP4buffer) + if (mp4->payloadBuffer) { #ifdef _WINDOWS _fseeki64(mp4->mediafp, (__int64) mp4->metaoffsets[index], SEEK_SET); #else fseeko(mp4->mediafp, (off_t) mp4->metaoffsets[index], SEEK_SET); #endif - fread(MP4buffer, 1, mp4->metasizes[index], mp4->mediafp); + fread(mp4->payloadBuffer, 1, mp4->metasizes[index], mp4->mediafp); mp4->filepos = mp4->metaoffsets[index] + mp4->metasizes[index]; - return MP4buffer; + return mp4->payloadBuffer; } } } - if (lastpayload) - free(lastpayload); return NULL; } @@ -201,7 +207,7 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) / break; } - if (!GPMF_VALID_FOURCC(qttag) && (qttag & 0xff) != 0xa9) // ©xyz and ©swr are allowed + if (!VALID_FOURCC(qttag) && (qttag & 0xff) != 0xa9) // ©xyz and ©swr are allowed { CloseSource((size_t)mp4); mp4 = NULL; @@ -937,6 +943,11 @@ void CloseSource(size_t handle) free(mp4->metastsc); mp4->metastsc = 0; } + if (mp4->payloadBuffer) + { + free(mp4->payloadBuffer); + mp4->payloadBuffer = 0; + } free(mp4); } @@ -945,9 +956,9 @@ void CloseSource(size_t handle) uint32_t GetPayloadTime(size_t handle, uint32_t index, double *in, double *out) { mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return GPMF_ERROR_MEMORY; + if (mp4 == NULL) return MP4_ERROR_MEMORY; - if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return GPMF_ERROR_MEMORY; + if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return MP4_ERROR_MEMORY; *in = ((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon); *out = ((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon); @@ -958,16 +969,16 @@ uint32_t GetPayloadTime(size_t handle, uint32_t index, double *in, double *out) // Add any Edit List offset *in += (double)mp4->metadataoffset_clockcount / (double)mp4->clockdemon; *out += (double)mp4->metadataoffset_clockcount / (double)mp4->clockdemon; - return GPMF_OK; + return MP4_ERROR_OK; } uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, int32_t *in_numerator, int32_t *out_numerator, uint32_t *denominator) { mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return GPMF_ERROR_MEMORY; + if (mp4 == NULL) return MP4_ERROR_MEMORY; - if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in_numerator == NULL || out_numerator == NULL) return GPMF_ERROR_MEMORY; + if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in_numerator == NULL || out_numerator == NULL) return MP4_ERROR_MEMORY; *in_numerator = (int32_t)(index * mp4->basemetadataduration); *out_numerator = (int32_t)((index + 1) * mp4->basemetadataduration); @@ -981,33 +992,33 @@ uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, int32_t *in_numer *denominator = mp4->meta_clockdemon; - return GPMF_OK; + return MP4_ERROR_OK; } uint32_t GetEditListOffset(size_t handle, double *offset) { mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return GPMF_ERROR_MEMORY; + if (mp4 == NULL) return MP4_ERROR_MEMORY; - if (mp4->clockdemon == 0) return GPMF_ERROR_MEMORY; + if (mp4->clockdemon == 0) return MP4_ERROR_MEMORY; *offset = (double)mp4->metadataoffset_clockcount / (double)mp4->clockdemon; - return GPMF_OK; + return MP4_ERROR_OK; } uint32_t GetEditListOffsetRationalTime(size_t handle, int32_t *offset_numerator, uint32_t *denominator) { mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return GPMF_ERROR_MEMORY; + if (mp4 == NULL) return MP4_ERROR_MEMORY; - if (mp4->clockdemon == 0) return GPMF_ERROR_MEMORY; + if (mp4->clockdemon == 0) return MP4_ERROR_MEMORY; *offset_numerator = mp4->metadataoffset_clockcount; *denominator = mp4->clockdemon; - return GPMF_OK; + return MP4_ERROR_OK; } @@ -1053,7 +1064,7 @@ size_t OpenMP4SourceUDTA(char *filename) len += fread(&qttag, 1, 4, mp4->mediafp); if (len == 8) { - if (!GPMF_VALID_FOURCC(qttag) && qttag != 0x7a7978a9) + if (!VALID_FOURCC(qttag) && qttag != 0x7a7978a9) { mp4->filepos += len; LongSeek(mp4, lastsize - 8 - len); @@ -1126,334 +1137,3 @@ size_t OpenMP4SourceUDTA(char *filename) return (size_t)mp4; } - -void SetTimeBaseStream(size_t handle, uint32_t fourcc) -{ - mp4object* mp4 = (mp4object*)handle; - if (mp4 == NULL) return; - - mp4->timeBaseFourCC = 0; - - if (!GPMF_VALID_FOURCC(fourcc)) return; - - mp4->timeBaseFourCC = fourcc; -} - - -double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags, double *firstsampletime, double *lastsampletime) -{ - mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return 0.0; - - GPMF_stream metadata_stream, *ms = &metadata_stream; - uint32_t teststart = 0; - uint32_t testend = mp4->indexcount; - double rate = 0.0; - - uint32_t *payload; - uint32_t payloadsize; - GPMF_ERR ret; - - if (mp4->indexcount < 1) - return 0.0; - - payload = GetPayload(handle, NULL, teststart); - payloadsize = GetPayloadSize(handle, teststart); - ret = GPMF_Init(ms, payload, payloadsize); - - if (ret != GPMF_OK) - goto cleanup; - - { - uint64_t basetimestamp = 0; - uint64_t starttimestamp = 0; - uint64_t endtimestamp = 0; - uint32_t startsamples = 0; - uint32_t endsamples = 0; - double intercept = 0.0; - - - - while (teststart < mp4->indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) - { - teststart++; - payload = GetPayload(handle, payload, teststart); // second last payload - payloadsize = GetPayloadSize(handle, teststart); - ret = GPMF_Init(ms, payload, payloadsize); - } - - if (ret == GPMF_OK && payload) - { - double startin, startout, endin, endout; - int usedTimeStamps = 0; - - uint32_t samples = GPMF_PayloadSampleCount(ms); - GPMF_stream find_stream; - GPMF_CopyState(ms, &find_stream); //ms is at the searched fourcc - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL)) - startsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)) - samples; - - GPMF_CopyState(ms, &find_stream); - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) - starttimestamp = BYTESWAP64(*(uint64_t *)GPMF_RawData(&find_stream)); - - if (starttimestamp) // how does this compare to other streams in this early payload? - { - GPMF_stream any_stream; - if (GPMF_OK == GPMF_Init(&any_stream, payload, payloadsize)) - { - basetimestamp = starttimestamp; - - if (mp4->timeBaseFourCC) - { - if (GPMF_OK == GPMF_FindNext(&any_stream, mp4->timeBaseFourCC, GPMF_RECURSE_LEVELS)) - { - if (GPMF_OK == GPMF_FindPrev(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) - { - basetimestamp = BYTESWAP64(*(uint64_t*)GPMF_RawData(&any_stream)); - } - } - - } - else - { - while (GPMF_OK == GPMF_FindNext(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_RECURSE_LEVELS)) - { - uint64_t timestamp = BYTESWAP64(*(uint64_t*)GPMF_RawData(&any_stream)); - if (timestamp < basetimestamp) - basetimestamp = timestamp; - } - } - } - } - //Note: basetimestamp is used the remove offset from the timestamp, - // however 0.0 may not be the same zero for your video or audio presentation time (although it should be close.) - // On GoPro camera, metadata streams like SHUT and ISOE are metadata fields associated with video, and these can be used - // to accurately sync meta with video. - - testend = mp4->indexcount; - do - { - testend--;// last payload with the fourcc needed - payload = GetPayload(handle, payload, testend); - payloadsize = GetPayloadSize(handle, testend); - ret = GPMF_Init(ms, payload, payloadsize); - } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); - - GetPayloadTime(handle, teststart, &startin, &startout); - GetPayloadTime(handle, testend, &endin, &endout); - - GPMF_CopyState(ms, &find_stream); - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL)) - endsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)); - else // If there is no TSMP we have to count the samples. - { - uint32_t i; - for (i = teststart; i <= testend; i++) - { - payload = GetPayload(handle,payload, i); // second last payload - payloadsize = GetPayloadSize(handle, i); - if (GPMF_OK == GPMF_Init(ms, payload, payloadsize)) - if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) - endsamples += GPMF_PayloadSampleCount(ms); - } - } - - if (starttimestamp != 0) - { - uint32_t last_samples = GPMF_PayloadSampleCount(ms); - uint32_t totaltimestamped_samples = endsamples - last_samples - startsamples; - double time_stamp_scale = 1000000000.0; // scan for nanoseconds, microseconds to seconds, all base 10. - - GPMF_CopyState(ms, &find_stream); - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) - endtimestamp = BYTESWAP64(*(uint64_t *)GPMF_RawData(&find_stream)); - - if (endtimestamp) - { - double approxrate = 0.0; - if (endsamples > startsamples) - approxrate = (double)(endsamples - startsamples) / (endout - startin); - - if (approxrate == 0.0) - approxrate = (double)(samples) / (endout - startin); - - - while (time_stamp_scale >= 1) - { - rate = (double)(totaltimestamped_samples) / ((double)(endtimestamp - starttimestamp) / time_stamp_scale); - if (rate*0.9 < approxrate && approxrate < rate*1.1) - break; - - time_stamp_scale *= 0.1; - } - if (time_stamp_scale < 1.0) rate = 0.0; - intercept = (((double)basetimestamp - (double)starttimestamp) / time_stamp_scale) * rate; - usedTimeStamps = 1; - } - } - - if (rate == 0.0) //Timestamps didn't help, or weren't available - { - if (!(flags & GPMF_SAMPLE_RATE_PRECISE)) - { - if (endsamples > startsamples) - rate = (double)(endsamples - startsamples) / (endout - startin); - - if (rate == 0.0) - rate = (double)(samples) / (endout - startin); - - intercept = (double)-startin * rate; - } - else // for increased precision, for older GPMF streams sometimes missing the total sample count - { - uint32_t payloadpos = 0, payloadcount = 0; - double slope, top = 0.0, bot = 0.0, meanX = 0, meanY = 0; - uint32_t *repeatarray = (uint32_t *)malloc(mp4->indexcount * 4 + 4); - memset(repeatarray, 0, mp4->indexcount * 4 + 4); - - samples = 0; - - for (payloadpos = teststart; payloadpos <= testend; payloadpos++) - { - payload = GetPayload(handle, payload, payloadpos); // second last payload - payloadsize = GetPayloadSize(handle, payloadpos); - ret = GPMF_Init(ms, payload, payloadsize); - - if (ret != GPMF_OK) - goto cleanup; - - if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) - { - GPMF_stream find_stream2; - GPMF_CopyState(ms, &find_stream2); - - payloadcount++; - - if (GPMF_OK == GPMF_FindNext(&find_stream2, fourcc, GPMF_CURRENT_LEVEL)) // Count the instances, not the repeats - { - if (repeatarray) - { - double in, out; - - do - { - samples++; - } while (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_CURRENT_LEVEL)); - - repeatarray[payloadpos] = samples; - meanY += (double)samples; - - if (GPMF_OK == GetPayloadTime(handle, payloadpos, &in, &out)) - meanX += out; - } - } - else - { - uint32_t repeat = GPMF_PayloadSampleCount(ms); - samples += repeat; - - if (repeatarray) - { - double in, out; - - repeatarray[payloadpos] = samples; - meanY += (double)samples; - - if (GPMF_OK == GetPayloadTime(handle, payloadpos, &in, &out)) - meanX += out; - } - } - } - else - { - repeatarray[payloadpos] = 0; - } - } - - // Compute the line of best fit for a jitter removed sample rate. - // This does assume an unchanging clock, even though the IMU data can thermally impacted causing small clock changes. - // TODO: Next enhancement would be a low order polynominal fit the compensate for any thermal clock drift. - if (repeatarray) - { - meanY /= (double)payloadcount; - meanX /= (double)payloadcount; - - for (payloadpos = teststart; payloadpos <= testend; payloadpos++) - { - double in, out; - if (repeatarray[payloadpos] && GPMF_OK == GetPayloadTime(handle, payloadpos, &in, &out)) - { - top += ((double)out - meanX)*((double)repeatarray[payloadpos] - meanY); - bot += ((double)out - meanX)*((double)out - meanX); - } - } - - slope = top / bot; - rate = slope; - - // This sample code might be useful for compare data latency between channels. - intercept = meanY - slope * meanX; -#if 0 - printf("%c%c%c%c start offset = %f (%.3fms) rate = %f\n", PRINTF_4CC(fourcc), intercept, 1000.0 * intercept / slope, rate); - printf("%c%c%c%c first sample at time %.3fms\n", PRINTF_4CC(fourcc), -1000.0 * intercept / slope); -#endif - } - else - { - rate = (double)(samples) / (endout - startin); - } - - free(repeatarray); - } - } - - if (firstsampletime && lastsampletime) - { - uint32_t endpayload = mp4->indexcount; - do - { - endpayload--;// last payload with the fourcc needed - payload = GetPayload(handle, payload, endpayload); - payloadsize = GetPayloadSize(handle, endpayload); - ret = GPMF_Init(ms, payload, payloadsize); - } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); - - if (endpayload > 0 && ret == GPMF_OK) - { - uint32_t totalsamples = endsamples - startsamples; - float timo = 0.0; - - GPMF_CopyState(ms, &find_stream); - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_OFFSET, GPMF_CURRENT_LEVEL)) - GPMF_FormattedData(&find_stream, &timo, 4, 0, 1); - - double first, last; - first = -intercept / rate - timo; - last = first + (double)totalsamples / rate; - - //Apply any Edit List corrections. - if (usedTimeStamps) // clips with STMP have the Edit List already applied via GetPayloadTime() - { - first += (double)mp4->metadataoffset_clockcount / (double)mp4->clockdemon; - last += (double)mp4->metadataoffset_clockcount / (double)mp4->clockdemon; - } - - //printf("%c%c%c%c first sample at time %.3fms, last at %.3fms\n", PRINTF_4CC(fourcc), 1000.0*first, 1000.0*last); - - if (firstsampletime) *firstsampletime = first; - - if (lastsampletime) *lastsampletime = last; - } - } - } - } - -cleanup: - if (payload) - FreePayload(payload); - payload = NULL; - - return rate; -} - diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h index e8f81c4..ddf2f7e 100644 --- a/demo/GPMF_mp4reader.h +++ b/demo/GPMF_mp4reader.h @@ -2,7 +2,7 @@ * * @brief Way Too Crude MP4|MOV reader * -* @version 1.8.0 +* @version 2.0.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -23,8 +23,6 @@ #ifndef _GPMF_MP4READER_H #define _GPMF_MP4READER_H -#include "../GPMF_parser.h" - #ifdef __cplusplus extern "C" { #endif @@ -74,6 +72,9 @@ typedef struct mp4object uint64_t filesize; uint64_t filepos; + uint32_t *payloadBuffer; + uint32_t payloadBufferSize; + uint32_t timeBaseFourCC; } mp4object; @@ -86,6 +87,11 @@ typedef struct mp4object #define NOSWAP8(a) (a) +typedef enum MP4READER_ERROR +{ + MP4_ERROR_OK = 0, + MP4_ERROR_MEMORY +} MP4READER_ERROR; #define MOV_GPMF_TRAK_TYPE MAKEID('m', 'e', 't', 'a') // track is the type for metadata @@ -112,7 +118,7 @@ void CloseSource(size_t handle); float GetDuration(size_t handle); uint32_t GetVideoFrameRateAndCount(size_t handle, uint32_t *numer, uint32_t *demon); uint32_t GetNumberPayloads(size_t handle); -uint32_t *GetPayload(size_t handle, uint32_t *lastpayload, uint32_t index); +uint32_t *GetPayload(size_t handle, uint32_t index); uint32_t WritePayload(size_t handle, uint32_t* payload, uint32_t payloadsize, uint32_t index); void FreePayload(uint32_t *lastpayload); uint32_t GetPayloadSize(size_t handle, uint32_t index); @@ -121,12 +127,6 @@ uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, int32_t *in_numer uint32_t GetEditListOffset(size_t handle, double *offset); uint32_t GetEditListOffsetRationalTime(size_t handle, int32_t *offset_numerator, uint32_t *denominator); -#define GPMF_SAMPLE_RATE_FAST 0 -#define GPMF_SAMPLE_RATE_PRECISE 1 - -void SetTimeBaseStream(size_t handle, uint32_t fourcc); // Called before GetGPMFSampleRate if you what the returned in/out times to be relative to a particular stream (like GPS5 or SHUT etc.), rather than the earliest time -double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags, double *in, double *out); - #ifdef __cplusplus } #endif diff --git a/demo/GPMF_utils.c b/demo/GPMF_utils.c new file mode 100644 index 0000000..3bb68f7 --- /dev/null +++ b/demo/GPMF_utils.c @@ -0,0 +1,352 @@ +/*! @file GPMF_utils.c + * + * @brief Utilities GPMF and MP4 handling + * + * @version 1.0.0 + * + * (C) Copyright 2020 GoPro Inc (http://gopro.com/). + * + * Licensed under either: + * - Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0 + * - MIT license, http://opensource.org/licenses/MIT + * at your option. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include + +#include "../GPMF_parser.h" +#include "GPMF_mp4reader.h" +#include "GPMF_utils.h" + + +double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFourCC, uint32_t flags, double *firstsampletime, double *lastsampletime) +{ + if (cb.mp4handle == 0) + return 0.0; + + uint32_t indexcount = cb.cbGetNumberPayloads(cb.mp4handle); + + GPMF_stream metadata_stream, *ms = &metadata_stream; + uint32_t teststart = 0; + uint32_t testend = indexcount; + double rate = 0.0; + + uint32_t *payload; + uint32_t payloadsize; + GPMF_ERR ret; + + if (indexcount < 1) + return 0.0; + + + payload = cb.cbGetPayload(cb.mp4handle, teststart); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + ret = GPMF_Init(ms, payload, payloadsize); + + if (ret != GPMF_OK) + goto cleanup; + + { + uint64_t basetimestamp = 0; + uint64_t starttimestamp = 0; + uint64_t endtimestamp = 0; + uint32_t startsamples = 0; + uint32_t endsamples = 0; + double intercept = 0.0; + + + + while (teststart < indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + { + teststart++; + payload = cb.cbGetPayload(cb.mp4handle, teststart); // second last payload + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + ret = GPMF_Init(ms, payload, payloadsize); + } + + if (ret == GPMF_OK && payload) + { + double startin, startout, endin, endout; + int usedTimeStamps = 0; + + uint32_t samples = GPMF_PayloadSampleCount(ms); + GPMF_stream find_stream; + GPMF_CopyState(ms, &find_stream); //ms is at the searched fourcc + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL)) + startsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)) - samples; + + GPMF_CopyState(ms, &find_stream); + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) + starttimestamp = BYTESWAP64(*(uint64_t *)GPMF_RawData(&find_stream)); + + if (starttimestamp) // how does this compare to other streams in this early payload? + { + GPMF_stream any_stream; + if (GPMF_OK == GPMF_Init(&any_stream, payload, payloadsize)) + { + basetimestamp = starttimestamp; + + if (timeBaseFourCC) + { + if (GPMF_OK == GPMF_FindNext(&any_stream, timeBaseFourCC, GPMF_RECURSE_LEVELS)) + { + if (GPMF_OK == GPMF_FindPrev(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) + { + basetimestamp = BYTESWAP64(*(uint64_t*)GPMF_RawData(&any_stream)); + } + } + } + else + { + while (GPMF_OK == GPMF_FindNext(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_RECURSE_LEVELS)) + { + uint64_t timestamp = BYTESWAP64(*(uint64_t*)GPMF_RawData(&any_stream)); + if (timestamp < basetimestamp) + basetimestamp = timestamp; + } + } + } + } + //Note: basetimestamp is used the remove offset from the timestamp, + // however 0.0 may not be the same zero for your video or audio presentation time (although it should be close.) + // On GoPro camera, metadata streams like SHUT and ISOE are metadata fields associated with video, and these can be used + // to accurately sync meta with video. + + testend = indexcount; + do + { + testend--;// last payload with the fourcc needed + payload = cb.cbGetPayload(cb.mp4handle, testend); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, testend); + ret = GPMF_Init(ms, payload, payloadsize); + } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); + + GetPayloadTime(cb.mp4handle, teststart, &startin, &startout); + GetPayloadTime(cb.mp4handle, testend, &endin, &endout); + + GPMF_CopyState(ms, &find_stream); + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL)) + endsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)); + else // If there is no TSMP we have to count the samples. + { + uint32_t i; + for (i = teststart; i <= testend; i++) + { + payload = cb.cbGetPayload(cb.mp4handle, i); // second last payload + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, i); + if (GPMF_OK == GPMF_Init(ms, payload, payloadsize)) + if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + endsamples += GPMF_PayloadSampleCount(ms); + } + } + + if (starttimestamp != 0) + { + uint32_t last_samples = GPMF_PayloadSampleCount(ms); + uint32_t totaltimestamped_samples = endsamples - last_samples - startsamples; + double time_stamp_scale = 1000000000.0; // scan for nanoseconds, microseconds to seconds, all base 10. + + GPMF_CopyState(ms, &find_stream); + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) + endtimestamp = BYTESWAP64(*(uint64_t *)GPMF_RawData(&find_stream)); + + if (endtimestamp) + { + double approxrate = 0.0; + if (endsamples > startsamples) + approxrate = (double)(endsamples - startsamples) / (endout - startin); + + if (approxrate == 0.0) + approxrate = (double)(samples) / (endout - startin); + + + while (time_stamp_scale >= 1) + { + rate = (double)(totaltimestamped_samples) / ((double)(endtimestamp - starttimestamp) / time_stamp_scale); + if (rate*0.9 < approxrate && approxrate < rate*1.1) + break; + + time_stamp_scale *= 0.1; + } + if (time_stamp_scale < 1.0) rate = 0.0; + intercept = (((double)basetimestamp - (double)starttimestamp) / time_stamp_scale) * rate; + usedTimeStamps = 1; + } + } + + if (rate == 0.0) //Timestamps didn't help, or weren't available + { + if (!(flags & GPMF_SAMPLE_RATE_PRECISE)) + { + if (endsamples > startsamples) + rate = (double)(endsamples - startsamples) / (endout - startin); + + if (rate == 0.0) + rate = (double)(samples) / (endout - startin); + + intercept = (double)-startin * rate; + } + else // for increased precision, for older GPMF streams sometimes missing the total sample count + { + uint32_t payloadpos = 0, payloadcount = 0; + double slope, top = 0.0, bot = 0.0, meanX = 0, meanY = 0; + uint32_t *repeatarray = (uint32_t *)malloc(indexcount * 4 + 4); + memset(repeatarray, 0, indexcount * 4 + 4); + + samples = 0; + + for (payloadpos = teststart; payloadpos <= testend; payloadpos++) + { + payload = cb.cbGetPayload(cb.mp4handle, payloadpos); // second last payload + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, payloadpos); + ret = GPMF_Init(ms, payload, payloadsize); + + if (ret != GPMF_OK) + goto cleanup; + + if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + { + GPMF_stream find_stream2; + GPMF_CopyState(ms, &find_stream2); + + payloadcount++; + + if (GPMF_OK == GPMF_FindNext(&find_stream2, fourcc, GPMF_CURRENT_LEVEL)) // Count the instances, not the repeats + { + if (repeatarray) + { + double in, out; + + do + { + samples++; + } while (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_CURRENT_LEVEL)); + + repeatarray[payloadpos] = samples; + meanY += (double)samples; + + if (GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + meanX += out; + } + } + else + { + uint32_t repeat = GPMF_PayloadSampleCount(ms); + samples += repeat; + + if (repeatarray) + { + double in, out; + + repeatarray[payloadpos] = samples; + meanY += (double)samples; + + if (GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + meanX += out; + } + } + } + else + { + repeatarray[payloadpos] = 0; + } + } + + // Compute the line of best fit for a jitter removed sample rate. + // This does assume an unchanging clock, even though the IMU data can thermally impacted causing small clock changes. + // TODO: Next enhancement would be a low order polynominal fit the compensate for any thermal clock drift. + if (repeatarray) + { + meanY /= (double)payloadcount; + meanX /= (double)payloadcount; + + for (payloadpos = teststart; payloadpos <= testend; payloadpos++) + { + double in, out; + if (repeatarray[payloadpos] && GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + { + top += ((double)out - meanX)*((double)repeatarray[payloadpos] - meanY); + bot += ((double)out - meanX)*((double)out - meanX); + } + } + + slope = top / bot; + rate = slope; + + // This sample code might be useful for compare data latency between channels. + intercept = meanY - slope * meanX; +#if 0 + printf("%c%c%c%c start offset = %f (%.3fms) rate = %f\n", PRINTF_4CC(fourcc), intercept, 1000.0 * intercept / slope, rate); + printf("%c%c%c%c first sample at time %.3fms\n", PRINTF_4CC(fourcc), -1000.0 * intercept / slope); +#endif + } + else + { + rate = (double)(samples) / (endout - startin); + } + + free(repeatarray); + } + } + + if (firstsampletime && lastsampletime) + { + uint32_t endpayload = indexcount; + do + { + endpayload--;// last payload with the fourcc needed + payload = cb.cbGetPayload(cb.mp4handle, endpayload); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, endpayload); + ret = GPMF_Init(ms, payload, payloadsize); + } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); + + if (endpayload > 0 && ret == GPMF_OK) + { + uint32_t totalsamples = endsamples - startsamples; + float timo = 0.0; + + GPMF_CopyState(ms, &find_stream); + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TIME_OFFSET, GPMF_CURRENT_LEVEL)) + GPMF_FormattedData(&find_stream, &timo, 4, 0, 1); + + double first, last; + first = -intercept / rate - timo; + last = first + (double)totalsamples / rate; + + //Apply any Edit List corrections. + if (usedTimeStamps) // clips with STMP have the Edit List already applied via GetPayloadTime() + { + if (cb.cbGetEditListOffsetRationalTime) + { + int32_t num = 0; + uint32_t dem = 1; + cb.cbGetEditListOffsetRationalTime(cb.mp4handle, &num, &dem); + first += (double)num / (double)dem; + last += (double)num / (double)dem; + } + } + + //printf("%c%c%c%c first sample at time %.3fms, last at %.3fms\n", PRINTF_4CC(fourcc), 1000.0*first, 1000.0*last); + + if (firstsampletime) *firstsampletime = first; + + if (lastsampletime) *lastsampletime = last; + } + } + } + } + +cleanup: + return rate; +} + diff --git a/demo/GPMF_utils.h b/demo/GPMF_utils.h new file mode 100644 index 0000000..cc79287 --- /dev/null +++ b/demo/GPMF_utils.h @@ -0,0 +1,49 @@ +/*! @file GPMF_utils.h +* +* @brief Utilities GPMF and MP4 handling +* +* @version 1.0.0 +* +* (C) Copyright 2020 GoPro Inc (http://gopro.com/). +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*/ + +#ifndef _GPMF_UTILS_H +#define _GPMF_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define GPMF_SAMPLE_RATE_FAST 0 +#define GPMF_SAMPLE_RATE_PRECISE 1 + +typedef struct mp4callbacks +{ + size_t mp4handle; + uint32_t (*cbGetNumberPayloads)(size_t handle); // number of indexed GPMF payloads + uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index + uint32_t* (*cbGetPayload)(size_t handle, uint32_t index); // get payload data for a particular index + uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track + int32_t* offset_numerator, uint32_t* denominator); +} mp4callbacks; + +double GetGPMFSampleRate(mp4callbacks cbobject, uint32_t fourcc, uint32_t timeBaseFourCC, uint32_t flags, double* in, double* out); + +#ifdef __cplusplus +} +#endif + +#endif From 4b04184f867241e78d368353cc05a8f2eb76ae1a Mon Sep 17 00:00:00 2001 From: Otto Sievert Date: Mon, 26 Oct 2020 15:27:35 -0700 Subject: [PATCH 02/19] adding GPDeps file --- GPDeps | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 GPDeps diff --git a/GPDeps b/GPDeps new file mode 100644 index 0000000..38af4cf --- /dev/null +++ b/GPDeps @@ -0,0 +1,10 @@ +{ + "version": "1.0.0", + + "info": { + "type": "lib", + "visibility": "public", + "platforms": ["android", "ios", "linux", "macos", "win"] + } +} + From a1fa74edfa50ee33e67956f2f015aab099ba221b Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 26 Oct 2020 16:43:32 -0700 Subject: [PATCH 03/19] improvements to GetGPMFSampleRate handling invalid types --- demo/GPMF_demo.c | 4 ++-- demo/GPMF_utils.c | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index 8ab23fe..93ee237 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -2,7 +2,7 @@ * * @brief Demo to extract GPMF from an MP4 * - * @version 2.1.0 + * @version 2.2.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -403,7 +403,7 @@ int main(int argc, char* argv[]) uint32_t fourcc = GPMF_Key(ms); - double rate = GetGPMFSampleRate(cbobject, fourcc, 0, GPMF_SAMPLE_RATE_PRECISE, &start, &end);// GPMF_SAMPLE_RATE_FAST); + double rate = GetGPMFSampleRate(cbobject, fourcc, STR2FOURCC("SHUT"), GPMF_SAMPLE_RATE_PRECISE, &start, &end);// GPMF_SAMPLE_RATE_FAST); printf(" %c%c%c%c sampling rate = %fHz (time %f to %f)\",\n", PRINTF_4CC(fourcc), rate, start, end); } } diff --git a/demo/GPMF_utils.c b/demo/GPMF_utils.c index 3bb68f7..6f600d0 100644 --- a/demo/GPMF_utils.c +++ b/demo/GPMF_utils.c @@ -66,7 +66,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour - while (teststart < indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + while (teststart < indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { teststart++; payload = cb.cbGetPayload(cb.mp4handle, teststart); // second last payload @@ -98,7 +98,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour if (timeBaseFourCC) { - if (GPMF_OK == GPMF_FindNext(&any_stream, timeBaseFourCC, GPMF_RECURSE_LEVELS)) + if (GPMF_OK == GPMF_FindNext(&any_stream, timeBaseFourCC, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { if (GPMF_OK == GPMF_FindPrev(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_CURRENT_LEVEL)) { @@ -108,7 +108,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour } else { - while (GPMF_OK == GPMF_FindNext(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_RECURSE_LEVELS)) + while (GPMF_OK == GPMF_FindNext(&any_stream, GPMF_KEY_TIME_STAMP, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { uint64_t timestamp = BYTESWAP64(*(uint64_t*)GPMF_RawData(&any_stream)); if (timestamp < basetimestamp) @@ -129,7 +129,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour payload = cb.cbGetPayload(cb.mp4handle, testend); payloadsize = cb.cbGetPayloadSize(cb.mp4handle, testend); ret = GPMF_Init(ms, payload, payloadsize); - } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); + } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); GetPayloadTime(cb.mp4handle, teststart, &startin, &startout); GetPayloadTime(cb.mp4handle, testend, &endin, &endout); @@ -145,7 +145,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour payload = cb.cbGetPayload(cb.mp4handle, i); // second last payload payloadsize = cb.cbGetPayloadSize(cb.mp4handle, i); if (GPMF_OK == GPMF_Init(ms, payload, payloadsize)) - if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) endsamples += GPMF_PayloadSampleCount(ms); } } @@ -214,7 +214,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour if (ret != GPMF_OK) goto cleanup; - if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)) + if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { GPMF_stream find_stream2; GPMF_CopyState(ms, &find_stream2); @@ -308,7 +308,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour payload = cb.cbGetPayload(cb.mp4handle, endpayload); payloadsize = cb.cbGetPayloadSize(cb.mp4handle, endpayload); ret = GPMF_Init(ms, payload, payloadsize); - } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS)); + } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); if (endpayload > 0 && ret == GPMF_OK) { From 02e9bb8e12d1906f43b2ab068dc7e0baa76cfb89 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 26 Oct 2020 16:56:51 -0700 Subject: [PATCH 04/19] forgotten version bump --- GPMF_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPMF_parser.c b/GPMF_parser.c index e79ea7b..c5c55be 100644 --- a/GPMF_parser.c +++ b/GPMF_parser.c @@ -2,7 +2,7 @@ * * @brief GPMF Parser library * - * @version 2.1.0 + * @version 2.2.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * From 1ec7fcec4b937ef2408dd1bd81a0025c78814192 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Wed, 28 Oct 2020 18:45:49 -0700 Subject: [PATCH 05/19] fix for handlinh stsz will all the same size fields. --- GPMF_common.h | 2 +- demo/GPMF_mp4reader.c | 4 ++-- demo/GPMF_mp4reader.h | 2 -- demo/GPMF_utils.h | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/GPMF_common.h b/GPMF_common.h index 85edbf3..fa7fee9 100644 --- a/GPMF_common.h +++ b/GPMF_common.h @@ -1,4 +1,4 @@ -/*! @file GPMF_parser.h +/*! @file GPMF_common.h * * @brief GPMF Parser library include * diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index 71bfc42..7d2f5c2 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -2,7 +2,7 @@ * * @brief Way Too Crude MP4|MOV reader * -* @version 2.0.0 +* @version 2.0.1 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -487,7 +487,7 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) / free(mp4->metasizes); mp4->metasizes = 0; } - if(num > 0 && qtsize > (num * 4)) + if(num > 0 && qtsize > (num * 4) || (equalsamplesize != 0 && qtsize == 20)) { mp4->metasize_count = num; mp4->metasizes = (uint32_t *)malloc(num * 4); diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h index ddf2f7e..1924748 100644 --- a/demo/GPMF_mp4reader.h +++ b/demo/GPMF_mp4reader.h @@ -74,8 +74,6 @@ typedef struct mp4object uint32_t *payloadBuffer; uint32_t payloadBufferSize; - - uint32_t timeBaseFourCC; } mp4object; #define MAKEID(a,b,c,d) (((d&0xff)<<24)|((c&0xff)<<16)|((b&0xff)<<8)|(a&0xff)) diff --git a/demo/GPMF_utils.h b/demo/GPMF_utils.h index cc79287..9148343 100644 --- a/demo/GPMF_utils.h +++ b/demo/GPMF_utils.h @@ -37,7 +37,7 @@ typedef struct mp4callbacks uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index uint32_t* (*cbGetPayload)(size_t handle, uint32_t index); // get payload data for a particular index uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track - int32_t* offset_numerator, uint32_t* denominator); + int32_t* offset_numerator, uint32_t* denominator); } mp4callbacks; double GetGPMFSampleRate(mp4callbacks cbobject, uint32_t fourcc, uint32_t timeBaseFourCC, uint32_t flags, double* in, double* out); From e8e8f4f7e7d592f606512d0d5418707352eae8e9 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Thu, 29 Oct 2020 15:13:58 -0700 Subject: [PATCH 06/19] Added API calls GetPayloadResource and FreePayloadResource --- GPMF_parser.c | 6 +-- demo/GPMF_demo.c | 20 +++++---- demo/GPMF_mp4reader.c | 96 +++++++++++++++++++++++++++++++------------ demo/GPMF_mp4reader.h | 21 ++++++---- demo/GPMF_utils.c | 29 +++++++------ demo/GPMF_utils.h | 14 ++++--- 6 files changed, 123 insertions(+), 63 deletions(-) diff --git a/GPMF_parser.c b/GPMF_parser.c index c5c55be..9abb6db 100644 --- a/GPMF_parser.c +++ b/GPMF_parser.c @@ -2,7 +2,7 @@ * * @brief GPMF Parser library * - * @version 2.2.0 + * @version 2.2.1 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -75,7 +75,7 @@ GPMF_ERR GPMF_Validate(GPMF_stream *ms, GPMF_LEVELS recurse) { if (ms) { - GPMF_ERROR ret = GPMF_OK; + GPMF_ERR ret = GPMF_OK; uint32_t currpos = ms->pos; uint32_t nestsize = ms->nest_size[ms->nest_level]; if (nestsize == 0 && ms->nest_level == 0) @@ -570,7 +570,7 @@ GPMF_ERR GPMF_SeekToSamples(GPMF_stream *ms) if (ms->pos + 1 < ms->buffer_size_longs) { - GPMF_ERROR ret = GPMF_OK; + GPMF_ERR ret = GPMF_OK; uint32_t size, type = GPMF_SAMPLE_TYPE(ms->buffer[ms->pos + 1]); memcpy(&prevstate, ms, sizeof(GPMF_stream)); diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index 93ee237..02d31f7 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -2,7 +2,7 @@ * * @brief Demo to extract GPMF from an MP4 * - * @version 2.2.0 + * @version 2.3.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -62,8 +62,9 @@ int main(int argc, char* argv[]) GPMF_ERR ret = GPMF_OK; GPMF_stream metadata_stream, * ms = &metadata_stream; double metadatalength; - uint32_t payloadsize; - uint32_t* payload = NULL; //buffer to store GPMF samples from the MP4. + uint32_t* payload = NULL; + uint32_t payloadsize = 0; + size_t payloadres = 0; uint32_t show_all_payloads = SHOW_ALL_PAYLOADS; uint32_t show_gpmf_structure = SHOW_GPMF_STRUCTURE; @@ -135,7 +136,8 @@ int main(int argc, char* argv[]) { double in = 0.0, out = 0.0; //times payloadsize = GetPayloadSize(mp4, index); - payload = GetPayload(mp4, index); + payloadres = GetPayloadResource(payloadres, payloadsize); + payload = GetPayload(mp4, payloadres, index); if (payload == NULL) goto cleanup; @@ -181,7 +183,7 @@ int main(int argc, char* argv[]) printf(" "); PrintGPMF(ms); // printf current GPMF KLV - nextret = GPMF_Next(ms, GPMF_RECURSE_LEVELS); + nextret = GPMF_Next(ms, GPMF_RECURSE_LEVELS | GPMF_TOLERANT); while(nextret == GPMF_ERROR_UNKNOWN_TYPE) // or just using GPMF_Next(ms, GPMF_RECURSE_LEVELS|GPMF_TOLERANT) to ignore and skip unknown types nextret = GPMF_Next(ms, GPMF_RECURSE_LEVELS); @@ -380,10 +382,9 @@ int main(int argc, char* argv[]) GPMF_ResetState(ms); } } - - GPMF_Free(ms); } + if (show_computed_samplerates) { mp4callbacks cbobject; @@ -391,11 +392,13 @@ int main(int argc, char* argv[]) cbobject.cbGetNumberPayloads = GetNumberPayloads; cbobject.cbGetPayload = GetPayload; cbobject.cbGetPayloadSize = GetPayloadSize; + cbobject.cbGetPayloadResource = GetPayloadResource; + cbobject.cbFreePayloadResource = FreePayloadResource; cbobject.cbGetEditListOffsetRationalTime = GetEditListOffsetRationalTime; printf("COMPUTED SAMPLERATES:\n"); // Find all the available Streams and compute they sample rates - while (GPMF_OK == GPMF_FindNext(ms, GPMF_KEY_STREAM, GPMF_RECURSE_LEVELS|GPMF_TOLERANT)) + while (GPMF_OK == GPMF_FindNext(ms, GPMF_KEY_STREAM, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { if (GPMF_OK == GPMF_SeekToSamples(ms)) //find the last FOURCC within the stream { @@ -410,6 +413,7 @@ int main(int argc, char* argv[]) } cleanup: + if (payloadres) FreePayloadResource(payloadres); if (ms) GPMF_Free(ms); CloseSource(mp4); } diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index 7d2f5c2..f62dfe8 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -51,37 +51,92 @@ uint32_t GetNumberPayloads(size_t handle) return 0; } -uint32_t *GetPayload(size_t handle, uint32_t index) + +size_t GetPayloadResource(size_t resHandle, uint32_t payloadsize) { - mp4object *mp4 = (mp4object *)handle; - if (mp4 == NULL) return NULL; + resObject* res = (resObject*)resHandle; - if (index < mp4->indexcount && mp4->mediafp) + if (res == NULL) { - if ((mp4->filesize >= mp4->metaoffsets[index]+mp4->metasizes[index]) && (mp4->metasizes[index] > 0)) + res = (resObject*)malloc(sizeof(resObject)); + if (res) + { + memset(res, 0, sizeof(resObject)); + resHandle = (size_t)res; + } + } + + if(res) + { + uint32_t myBufferSize = payloadsize + 256; + + if (res->buffer == NULL) { - uint32_t buffsizeneeded = mp4->metasizes[index] + 256; // Add a little more to limit reallocations - if (mp4->payloadBuffer == NULL) + res->buffer = malloc(myBufferSize); + if (res->buffer) { - mp4->payloadBuffer = (uint32_t*)malloc(buffsizeneeded); - mp4->payloadBufferSize = buffsizeneeded; + res->bufferSize = myBufferSize; } - else if (mp4->payloadBufferSize < mp4->metasizes[index]) + else + { + free(res); + resHandle = 0; + } + } + else if (payloadsize > res->bufferSize) + { + res->buffer = realloc(res->buffer, myBufferSize); + res->bufferSize = myBufferSize; + if (res->buffer == NULL) { - mp4->payloadBuffer = (uint32_t*)realloc((void*)mp4->payloadBuffer, buffsizeneeded); - mp4->payloadBufferSize = buffsizeneeded; + free(res); + resHandle = 0; } + } + } + + return resHandle; +} + + - if (mp4->payloadBuffer) +void FreePayloadResource(size_t resHandle) +{ + resObject* res = (resObject*)resHandle; + + if (res) + { + if (res->buffer) free(res->buffer); + free(res); + } +} + + +uint32_t *GetPayload(size_t handle, size_t resHandle, uint32_t index) +{ + mp4object *mp4 = (mp4object *)handle; + resObject *res = (resObject *)resHandle; + + if (mp4 == NULL) return NULL; + if (res == NULL) return NULL; + + if (index < mp4->indexcount && mp4->mediafp) + { + if ((mp4->filesize >= mp4->metaoffsets[index]+mp4->metasizes[index]) && (mp4->metasizes[index] > 0)) + { + uint32_t buffsizeneeded = mp4->metasizes[index]; // Add a little more to limit reallocations + + resHandle = GetPayloadResource(resHandle, buffsizeneeded); + if(resHandle) { #ifdef _WINDOWS _fseeki64(mp4->mediafp, (__int64) mp4->metaoffsets[index], SEEK_SET); #else fseeko(mp4->mediafp, (off_t) mp4->metaoffsets[index], SEEK_SET); #endif - fread(mp4->payloadBuffer, 1, mp4->metasizes[index], mp4->mediafp); + fread(res->buffer, 1, mp4->metasizes[index], mp4->mediafp); mp4->filepos = mp4->metaoffsets[index] + mp4->metasizes[index]; - return mp4->payloadBuffer; + return res->buffer; } } } @@ -135,12 +190,6 @@ void LongSeek(mp4object *mp4, int64_t offset) } } -void FreePayload(uint32_t *lastpayload) -{ - if (lastpayload) - free(lastpayload); -} - uint32_t GetPayloadSize(size_t handle, uint32_t index) { @@ -943,11 +992,6 @@ void CloseSource(size_t handle) free(mp4->metastsc); mp4->metastsc = 0; } - if (mp4->payloadBuffer) - { - free(mp4->payloadBuffer); - mp4->payloadBuffer = 0; - } free(mp4); } diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h index 1924748..faac0e2 100644 --- a/demo/GPMF_mp4reader.h +++ b/demo/GPMF_mp4reader.h @@ -71,11 +71,15 @@ typedef struct mp4object FILE *mediafp; uint64_t filesize; uint64_t filepos; - - uint32_t *payloadBuffer; - uint32_t payloadBufferSize; } mp4object; +typedef struct resObject +{ + uint32_t* buffer; + uint32_t bufferSize; +} resObject; + + #define MAKEID(a,b,c,d) (((d&0xff)<<24)|((c&0xff)<<16)|((b&0xff)<<8)|(a&0xff)) #define STR2FOURCC(s) ((s[0]<<0)|(s[1]<<8)|(s[2]<<16)|(s[3]<<24)) @@ -116,12 +120,13 @@ void CloseSource(size_t handle); float GetDuration(size_t handle); uint32_t GetVideoFrameRateAndCount(size_t handle, uint32_t *numer, uint32_t *demon); uint32_t GetNumberPayloads(size_t handle); -uint32_t *GetPayload(size_t handle, uint32_t index); uint32_t WritePayload(size_t handle, uint32_t* payload, uint32_t payloadsize, uint32_t index); -void FreePayload(uint32_t *lastpayload); -uint32_t GetPayloadSize(size_t handle, uint32_t index); -uint32_t GetPayloadTime(size_t handle, uint32_t index, double *in, double *out); //MP4 timestamps for the payload -uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, int32_t *in_numerator, int32_t *out_numerator, uint32_t *denominator); +size_t GetPayloadResource(size_t resHandle, uint32_t payloadBufferSize); +void FreePayloadResource(size_t resHandle); +uint32_t* GetPayload(size_t mp4Handle, size_t resHandle, uint32_t index); +uint32_t GetPayloadSize(size_t mp4Handle, uint32_t index); +uint32_t GetPayloadTime(size_t mp4Handle, uint32_t index, double *in, double *out); //MP4 timestamps for the payload +uint32_t GetPayloadRationalTime(size_t mp4Handle, uint32_t index, int32_t *in_numerator, int32_t *out_numerator, uint32_t *denominator); uint32_t GetEditListOffset(size_t handle, double *offset); uint32_t GetEditListOffsetRationalTime(size_t handle, int32_t *offset_numerator, uint32_t *denominator); diff --git a/demo/GPMF_utils.c b/demo/GPMF_utils.c index 6f600d0..24bdeb1 100644 --- a/demo/GPMF_utils.c +++ b/demo/GPMF_utils.c @@ -2,7 +2,7 @@ * * @brief Utilities GPMF and MP4 handling * - * @version 1.0.0 + * @version 1.1.0 * * (C) Copyright 2020 GoPro Inc (http://gopro.com/). * @@ -43,14 +43,17 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour uint32_t *payload; uint32_t payloadsize; + size_t payloadres = 0; + GPMF_ERR ret; if (indexcount < 1) return 0.0; - - payload = cb.cbGetPayload(cb.mp4handle, teststart); payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payloadres = cb.cbGetPayloadResource(payloadres, payloadsize); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); + ret = GPMF_Init(ms, payload, payloadsize); if (ret != GPMF_OK) @@ -69,8 +72,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour while (teststart < indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) { teststart++; - payload = cb.cbGetPayload(cb.mp4handle, teststart); // second last payload payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); // second last payload ret = GPMF_Init(ms, payload, payloadsize); } @@ -126,8 +129,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour do { testend--;// last payload with the fourcc needed - payload = cb.cbGetPayload(cb.mp4handle, testend); - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, testend); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); // second last payload ret = GPMF_Init(ms, payload, payloadsize); } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); @@ -142,8 +145,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour uint32_t i; for (i = teststart; i <= testend; i++) { - payload = cb.cbGetPayload(cb.mp4handle, i); // second last payload - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, i); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); if (GPMF_OK == GPMF_Init(ms, payload, payloadsize)) if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) endsamples += GPMF_PayloadSampleCount(ms); @@ -207,8 +210,9 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour for (payloadpos = teststart; payloadpos <= testend; payloadpos++) { - payload = cb.cbGetPayload(cb.mp4handle, payloadpos); // second last payload - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, payloadpos); + + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); // second last payload ret = GPMF_Init(ms, payload, payloadsize); if (ret != GPMF_OK) @@ -305,8 +309,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour do { endpayload--;// last payload with the fourcc needed - payload = cb.cbGetPayload(cb.mp4handle, endpayload); - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, endpayload); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); ret = GPMF_Init(ms, payload, payloadsize); } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); @@ -347,6 +351,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour } cleanup: + if (payloadres) cb.cbFreePayloadResource(payloadres); return rate; } diff --git a/demo/GPMF_utils.h b/demo/GPMF_utils.h index 9148343..ae5ab50 100644 --- a/demo/GPMF_utils.h +++ b/demo/GPMF_utils.h @@ -2,7 +2,7 @@ * * @brief Utilities GPMF and MP4 handling * -* @version 1.0.0 +* @version 1.1.0 * * (C) Copyright 2020 GoPro Inc (http://gopro.com/). * @@ -33,11 +33,13 @@ extern "C" { typedef struct mp4callbacks { size_t mp4handle; - uint32_t (*cbGetNumberPayloads)(size_t handle); // number of indexed GPMF payloads - uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index - uint32_t* (*cbGetPayload)(size_t handle, uint32_t index); // get payload data for a particular index - uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track - int32_t* offset_numerator, uint32_t* denominator); + uint32_t (*cbGetNumberPayloads)(size_t handle); // number of indexed GPMF payloads + uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index + uint32_t *(*cbGetPayload)(size_t handle, size_t res, uint32_t index); // get payload data for a particular index + size_t (*cbGetPayloadResource)(size_t reshandle, uint32_t initialMemorySize); // get payload memory handler + void (*cbFreePayloadResource)(size_t reshandle); // free payload memory handler + uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track + int32_t *offset_numerator, uint32_t* denominator); } mp4callbacks; double GetGPMFSampleRate(mp4callbacks cbobject, uint32_t fourcc, uint32_t timeBaseFourCC, uint32_t flags, double* in, double* out); From c2998cc2f215ad3d2c01503cf51532b519a53091 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 2 Nov 2020 14:26:30 -0800 Subject: [PATCH 07/19] Compiler warning fix --- demo/GPMF_mp4reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index f62dfe8..b888804 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -536,7 +536,7 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) / free(mp4->metasizes); mp4->metasizes = 0; } - if(num > 0 && qtsize > (num * 4) || (equalsamplesize != 0 && qtsize == 20)) + if(num > 0 && (qtsize > (num * 4)) || (equalsamplesize != 0 && qtsize == 20)) { mp4->metasize_count = num; mp4->metasizes = (uint32_t *)malloc(num * 4); From 6b036185023cc32544d3387c446ad3aac76e2735 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 2 Nov 2020 14:28:17 -0800 Subject: [PATCH 08/19] Compiler warning fix --- demo/GPMF_mp4reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index b888804..9535716 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -536,7 +536,7 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) / free(mp4->metasizes); mp4->metasizes = 0; } - if(num > 0 && (qtsize > (num * 4)) || (equalsamplesize != 0 && qtsize == 20)) + if(num > 0 && (qtsize > (num * 4) || (equalsamplesize != 0 && qtsize == 20))) { mp4->metasize_count = num; mp4->metasizes = (uint32_t *)malloc(num * 4); From b558e8f2689b1ffaec3ad387bfe9c0a675126c1b Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Tue, 3 Nov 2020 12:35:24 -0800 Subject: [PATCH 09/19] Added missing callback for GetPayloadTime and fixed GetGPMFSampleRate to using the correct payload number --- demo/GPMF_demo.c | 6 ++++-- demo/GPMF_utils.c | 30 +++++++++++++++--------------- demo/GPMF_utils.h | 5 +++-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index 02d31f7..cb7f308 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -2,7 +2,7 @@ * * @brief Demo to extract GPMF from an MP4 * - * @version 2.3.0 + * @version 2.4.0 * * (C) Copyright 2017-2020 GoPro Inc (http://gopro.com/). * @@ -136,7 +136,8 @@ int main(int argc, char* argv[]) { double in = 0.0, out = 0.0; //times payloadsize = GetPayloadSize(mp4, index); - payloadres = GetPayloadResource(payloadres, payloadsize); + payloadres = GetPayloadResource(payloadres, + payloadsize); payload = GetPayload(mp4, payloadres, index); if (payload == NULL) goto cleanup; @@ -393,6 +394,7 @@ int main(int argc, char* argv[]) cbobject.cbGetPayload = GetPayload; cbobject.cbGetPayloadSize = GetPayloadSize; cbobject.cbGetPayloadResource = GetPayloadResource; + cbobject.cbGetPayloadTime = GetPayloadTime; cbobject.cbFreePayloadResource = FreePayloadResource; cbobject.cbGetEditListOffsetRationalTime = GetEditListOffsetRationalTime; diff --git a/demo/GPMF_utils.c b/demo/GPMF_utils.c index 24bdeb1..5d9ef09 100644 --- a/demo/GPMF_utils.c +++ b/demo/GPMF_utils.c @@ -2,7 +2,7 @@ * * @brief Utilities GPMF and MP4 handling * - * @version 1.1.0 + * @version 1.2.0 * * (C) Copyright 2020 GoPro Inc (http://gopro.com/). * @@ -51,7 +51,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour return 0.0; payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payloadres = cb.cbGetPayloadResource(payloadres, payloadsize); + payloadres = cb.cbGetPayloadResource(0, payloadsize); payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); ret = GPMF_Init(ms, payload, payloadsize); @@ -129,13 +129,13 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour do { testend--;// last payload with the fourcc needed - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); // second last payload + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, testend); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, testend); // second last payload ret = GPMF_Init(ms, payload, payloadsize); } while (testend > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); - GetPayloadTime(cb.mp4handle, teststart, &startin, &startout); - GetPayloadTime(cb.mp4handle, testend, &endin, &endout); + cb.cbGetPayloadTime(cb.mp4handle, teststart, &startin, &startout); + cb.cbGetPayloadTime(cb.mp4handle, testend, &endin, &endout); GPMF_CopyState(ms, &find_stream); if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL)) @@ -145,8 +145,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour uint32_t i; for (i = teststart; i <= testend; i++) { - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, i); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, i); if (GPMF_OK == GPMF_Init(ms, payload, payloadsize)) if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)) endsamples += GPMF_PayloadSampleCount(ms); @@ -211,8 +211,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour for (payloadpos = teststart; payloadpos <= testend; payloadpos++) { - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); // second last payload + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, payloadpos); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, payloadpos); // second last payload ret = GPMF_Init(ms, payload, payloadsize); if (ret != GPMF_OK) @@ -239,7 +239,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour repeatarray[payloadpos] = samples; meanY += (double)samples; - if (GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + if (GPMF_OK == cb.cbGetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) meanX += out; } } @@ -255,7 +255,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour repeatarray[payloadpos] = samples; meanY += (double)samples; - if (GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + if (GPMF_OK == cb.cbGetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) meanX += out; } } @@ -277,7 +277,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour for (payloadpos = teststart; payloadpos <= testend; payloadpos++) { double in, out; - if (repeatarray[payloadpos] && GPMF_OK == GetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) + if (repeatarray[payloadpos] && GPMF_OK == cb.cbGetPayloadTime(cb.mp4handle, payloadpos, &in, &out)) { top += ((double)out - meanX)*((double)repeatarray[payloadpos] - meanY); bot += ((double)out - meanX)*((double)out - meanX); @@ -309,8 +309,8 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour do { endpayload--;// last payload with the fourcc needed - payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); + payloadsize = cb.cbGetPayloadSize(cb.mp4handle, endpayload); + payload = cb.cbGetPayload(cb.mp4handle, payloadres, endpayload); ret = GPMF_Init(ms, payload, payloadsize); } while (endpayload > 0 && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS | GPMF_TOLERANT)); diff --git a/demo/GPMF_utils.h b/demo/GPMF_utils.h index ae5ab50..010e788 100644 --- a/demo/GPMF_utils.h +++ b/demo/GPMF_utils.h @@ -2,7 +2,7 @@ * * @brief Utilities GPMF and MP4 handling * -* @version 1.1.0 +* @version 1.2.0 * * (C) Copyright 2020 GoPro Inc (http://gopro.com/). * @@ -37,7 +37,8 @@ typedef struct mp4callbacks uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index uint32_t *(*cbGetPayload)(size_t handle, size_t res, uint32_t index); // get payload data for a particular index size_t (*cbGetPayloadResource)(size_t reshandle, uint32_t initialMemorySize); // get payload memory handler - void (*cbFreePayloadResource)(size_t reshandle); // free payload memory handler + void (*cbFreePayloadResource)(size_t reshandle); // free payload memory handler + uint32_t (*cbGetPayloadTime)(size_t mp4Handle, uint32_t index, double* in, double* out); //MP4 timestamps for the payload uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track int32_t *offset_numerator, uint32_t* denominator); } mp4callbacks; From af676c3ed9a1ce69d93c03e0616b91c1021355bc Mon Sep 17 00:00:00 2001 From: Otto Sievert Date: Mon, 26 Oct 2020 15:27:35 -0700 Subject: [PATCH 10/19] adding GPDeps file --- GPDeps | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 GPDeps diff --git a/GPDeps b/GPDeps new file mode 100644 index 0000000..38af4cf --- /dev/null +++ b/GPDeps @@ -0,0 +1,10 @@ +{ + "version": "1.0.0", + + "info": { + "type": "lib", + "visibility": "public", + "platforms": ["android", "ios", "linux", "macos", "win"] + } +} + From 20f2e696cdf5768b4ad1c5b84e500fba44d8e176 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Sat, 28 Nov 2020 12:11:59 -0800 Subject: [PATCH 11/19] tmpbuffer free improvement --- demo/GPMF_demo.c | 136 ++++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 66 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index cb7f308..a13b208 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -290,94 +290,98 @@ int main(int argc, char* argv[]) GPMF_SampleType type = GPMF_Type(ms); uint32_t samples = GPMF_Repeat(ms); uint32_t elements = GPMF_ElementsInStruct(ms); - uint32_t buffersize = samples * elements * sizeof(double); - GPMF_stream find_stream; - double* ptr, * tmpbuffer = (double*)malloc(buffersize); + + if (samples) + { + uint32_t buffersize = samples * elements * sizeof(double); + GPMF_stream find_stream; + double* ptr, * tmpbuffer = (double*)malloc(buffersize); #define MAX_UNITS 64 #define MAX_UNITLEN 8 - char units[MAX_UNITS][MAX_UNITLEN] = { "" }; - uint32_t unit_samples = 1; - - char complextype[MAX_UNITS] = { "" }; - uint32_t type_samples = 1; + char units[MAX_UNITS][MAX_UNITLEN] = { "" }; + uint32_t unit_samples = 1; - if (tmpbuffer && samples) - { - uint32_t i, j; + char complextype[MAX_UNITS] = { "" }; + uint32_t type_samples = 1; - //Search for any units to display - GPMF_CopyState(ms, &find_stream); - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_SI_UNITS, GPMF_CURRENT_LEVEL|GPMF_TOLERANT) || - GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_UNITS, GPMF_CURRENT_LEVEL|GPMF_TOLERANT)) + if (tmpbuffer) { - char* data = (char*)GPMF_RawData(&find_stream); - uint32_t ssize = GPMF_StructSize(&find_stream); - if (ssize > MAX_UNITLEN - 1) ssize = MAX_UNITLEN - 1; - unit_samples = GPMF_Repeat(&find_stream); + uint32_t i, j; - for (i = 0; i < unit_samples && i < MAX_UNITS; i++) + //Search for any units to display + GPMF_CopyState(ms, &find_stream); + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_SI_UNITS, GPMF_CURRENT_LEVEL | GPMF_TOLERANT) || + GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_UNITS, GPMF_CURRENT_LEVEL | GPMF_TOLERANT)) { - memcpy(units[i], data, ssize); - units[i][ssize] = 0; - data += ssize; - } - } + char* data = (char*)GPMF_RawData(&find_stream); + uint32_t ssize = GPMF_StructSize(&find_stream); + if (ssize > MAX_UNITLEN - 1) ssize = MAX_UNITLEN - 1; + unit_samples = GPMF_Repeat(&find_stream); - //Search for TYPE if Complex - GPMF_CopyState(ms, &find_stream); - type_samples = 0; - if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TYPE, GPMF_CURRENT_LEVEL|GPMF_TOLERANT)) - { - char* data = (char*)GPMF_RawData(&find_stream); - uint32_t ssize = GPMF_StructSize(&find_stream); - if (ssize > MAX_UNITLEN - 1) ssize = MAX_UNITLEN - 1; - type_samples = GPMF_Repeat(&find_stream); + for (i = 0; i < unit_samples && i < MAX_UNITS; i++) + { + memcpy(units[i], data, ssize); + units[i][ssize] = 0; + data += ssize; + } + } - for (i = 0; i < type_samples && i < MAX_UNITS; i++) + //Search for TYPE if Complex + GPMF_CopyState(ms, &find_stream); + type_samples = 0; + if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TYPE, GPMF_CURRENT_LEVEL | GPMF_TOLERANT)) { - complextype[i] = data[i]; - } - } + char* data = (char*)GPMF_RawData(&find_stream); + uint32_t ssize = GPMF_StructSize(&find_stream); + if (ssize > MAX_UNITLEN - 1) ssize = MAX_UNITLEN - 1; + type_samples = GPMF_Repeat(&find_stream); - //GPMF_FormattedData(ms, tmpbuffer, buffersize, 0, samples); // Output data in LittleEnd, but no scale - if (GPMF_OK == GPMF_ScaledData(ms, tmpbuffer, buffersize, 0, samples, GPMF_TYPE_DOUBLE))//Output scaled data as floats - { + for (i = 0; i < type_samples && i < MAX_UNITS; i++) + { + complextype[i] = data[i]; + } + } - ptr = tmpbuffer; - int pos = 0; - for (i = 0; i < samples; i++) + //GPMF_FormattedData(ms, tmpbuffer, buffersize, 0, samples); // Output data in LittleEnd, but no scale + if (GPMF_OK == GPMF_ScaledData(ms, tmpbuffer, buffersize, 0, samples, GPMF_TYPE_DOUBLE))//Output scaled data as floats { - printf(" %c%c%c%c ", PRINTF_4CC(key)); - for (j = 0; j < elements; j++) + ptr = tmpbuffer; + int pos = 0; + for (i = 0; i < samples; i++) { - if (type == GPMF_TYPE_STRING_ASCII) - { - printf("%c", rawdata[pos]); - pos++; - ptr++; - } - else if (type_samples == 0) //no TYPE structure - printf("%.3f%s, ", *ptr++, units[j % unit_samples]); - else if (complextype[j] != 'F') - { - printf("%.3f%s, ", *ptr++, units[j % unit_samples]); - pos += GPMF_SizeofType((GPMF_SampleType)complextype[j]); - } - else if (type_samples && complextype[j] == GPMF_TYPE_FOURCC) + printf(" %c%c%c%c ", PRINTF_4CC(key)); + + for (j = 0; j < elements; j++) { - ptr++; - printf("%c%c%c%c, ", rawdata[pos], rawdata[pos + 1], rawdata[pos + 2], rawdata[pos + 3]); - pos += GPMF_SizeofType((GPMF_SampleType)complextype[j]); + if (type == GPMF_TYPE_STRING_ASCII) + { + printf("%c", rawdata[pos]); + pos++; + ptr++; + } + else if (type_samples == 0) //no TYPE structure + printf("%.3f%s, ", *ptr++, units[j % unit_samples]); + else if (complextype[j] != 'F') + { + printf("%.3f%s, ", *ptr++, units[j % unit_samples]); + pos += GPMF_SizeofType((GPMF_SampleType)complextype[j]); + } + else if (type_samples && complextype[j] == GPMF_TYPE_FOURCC) + { + ptr++; + printf("%c%c%c%c, ", rawdata[pos], rawdata[pos + 1], rawdata[pos + 2], rawdata[pos + 3]); + pos += GPMF_SizeofType((GPMF_SampleType)complextype[j]); + } } - } - printf("\n"); + printf("\n"); + } } + free(tmpbuffer); } - free(tmpbuffer); } } GPMF_ResetState(ms); From 131c88d3571c95ff72f54adbc91635f03e83f5be Mon Sep 17 00:00:00 2001 From: Yoann Laala Date: Tue, 1 Dec 2020 12:20:10 -0800 Subject: [PATCH 12/19] introduce mp4flag to make file read/write mode optional --- demo/GPMF_demo.c | 4 ++-- demo/GPMF_mp4reader.c | 14 ++++++++------ demo/GPMF_mp4reader.h | 9 +++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index a13b208..c4aafa2 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -83,9 +83,9 @@ int main(int argc, char* argv[]) } #if 1 // Search for GPMF Track - size_t mp4 = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE); + size_t mp4 = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE, 0); #else // look for a global GPMF payload in the moov header, within 'udta' - size_t mp4 = OpenMP4SourceUDTA(argv[1]); //Search for GPMF payload with MP4's udta + size_t mp4 = OpenMP4SourceUDTA(argv[1], 0); //Search for GPMF payload with MP4's udta #endif if (mp4 == 0) { diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index 9535716..29a8c3c 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -205,7 +205,7 @@ uint32_t GetPayloadSize(size_t handle, uint32_t index) #define MAX_NEST_LEVEL 20 -size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) //RAW or within MP4 +size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype, int32_t flags) //RAW or within MP4 { mp4object *mp4 = (mp4object *)malloc(sizeof(mp4object)); if (mp4 == NULL) return 0; @@ -227,10 +227,11 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype) / return 0; } + const char *mode = (flags & MP4_FLAG_READ_WRITE_MODE) ? "rb+" : "rb"; #ifdef _WINDOWS - fopen_s(&mp4->mediafp, filename, "rb+"); + fopen_s(&mp4->mediafp, filename, mode); #else - mp4->mediafp = fopen(filename, "rb+"); + mp4->mediafp = fopen(filename, mode); #endif if (mp4->mediafp) @@ -1067,7 +1068,7 @@ uint32_t GetEditListOffsetRationalTime(size_t handle, int32_t *offset_numerator, -size_t OpenMP4SourceUDTA(char *filename) +size_t OpenMP4SourceUDTA(char *filename, int32_t flags) { mp4object *mp4 = (mp4object *)malloc(sizeof(mp4object)); if (mp4 == NULL) return 0; @@ -1088,10 +1089,11 @@ size_t OpenMP4SourceUDTA(char *filename) return 0; } + const char *mode = (flags & MP4_FLAG_READ_WRITE_MODE) ? "rb+" : "rb"; #ifdef _WINDOWS - fopen_s(&mp4->mediafp, filename, "rb+"); + fopen_s(&mp4->mediafp, filename, mode); #else - mp4->mediafp = fopen(filename, "rb+"); + mp4->mediafp = fopen(filename, mode); #endif if (mp4->mediafp) diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h index faac0e2..012c64c 100644 --- a/demo/GPMF_mp4reader.h +++ b/demo/GPMF_mp4reader.h @@ -73,6 +73,11 @@ typedef struct mp4object uint64_t filepos; } mp4object; +enum mp4flag +{ + MP4_FLAG_READ_WRITE_MODE = 1 << 0, +}; + typedef struct resObject { uint32_t* buffer; @@ -114,8 +119,8 @@ typedef enum MP4READER_ERROR ( (((a>>8)&0xff)>='a'&&((a>>8)&0xff)<='z') || (((a>>8)&0xff)>='A'&&((a>>8)&0xff)<='Z') || (((a>>8)&0xff)>='0'&&((a>>8)&0xff)<='9') || (((a>>8)&0xff)==' ') ) && \ ( (((a>>0)&0xff)>='a'&&((a>>0)&0xff)<='z') || (((a>>0)&0xff)>='A'&&((a>>0)&0xff)<='Z') || (((a>>0)&0xff)>='0'&&((a>>0)&0xff)<='9') || (((a>>0)&0xff)==' ') )) -size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t subtype); -size_t OpenMP4SourceUDTA(char *filename); +size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t subtype, int32_t flags); +size_t OpenMP4SourceUDTA(char *filename, int32_t flags); void CloseSource(size_t handle); float GetDuration(size_t handle); uint32_t GetVideoFrameRateAndCount(size_t handle, uint32_t *numer, uint32_t *demon); From 1850a96e537edb30f510ff88f615ba903ab8a354 Mon Sep 17 00:00:00 2001 From: Yoann Laala Date: Mon, 7 Dec 2020 10:04:03 -0800 Subject: [PATCH 13/19] cmake: add missing file demo/GPMF_utils.c to the compilation --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 917fdb6..a36015c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_SUPPRESS_REGENERATION true) set(CMAKE_CONFIGURATION_TYPES "Debug;Release") file(GLOB LIB_SOURCES "GPMF_parser.c" "demo/GPMF_mp4reader.c") -file(GLOB SOURCES ${LIB_SOURCES} "demo/GPMF_demo.c" "demo/GPMF_print.c") +file(GLOB SOURCES ${LIB_SOURCES} "demo/GPMF_demo.c" "demo/GPMF_print.c" "demo/GPMF_utils.c") add_executable(GPMF_PARSER_BIN ${SOURCES}) set_target_properties(GPMF_PARSER_BIN PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") From b8d268b2473ea74620569258693dfbdc386250fc Mon Sep 17 00:00:00 2001 From: Yoann Laala Date: Mon, 7 Dec 2020 19:31:08 -0800 Subject: [PATCH 14/19] cmake: add missing header GPMF_utils.h --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a36015c..ff23669 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,4 +21,4 @@ install(TARGETS GPMF_PARSER_LIB DESTINATION "lib") install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION "lib/pkgconfig") install(FILES "GPMF_parser.h" "GPMF_common.h" DESTINATION "include/gpmf-parser") -install(FILES "demo/GPMF_mp4reader.h" DESTINATION "include/gpmf-parser/demo") +install(FILES "demo/GPMF_mp4reader.h" "demo/GPMF_utils.h" DESTINATION "include/gpmf-parser/demo") From 7aa7d5b2b41dd61e52a579d56e3dd0266ac6c64c Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Tue, 26 Jan 2021 13:58:51 -0800 Subject: [PATCH 15/19] all callbacks now also accpept the mp4handle as an input --- demo/GPMF_demo.c | 27 +++++++++++++-------------- demo/GPMF_mp4reader.c | 14 +++++++------- demo/GPMF_mp4reader.h | 18 +++++++++--------- demo/GPMF_utils.c | 4 ++-- demo/GPMF_utils.h | 16 ++++++++-------- 5 files changed, 39 insertions(+), 40 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index a13b208..4651382 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -83,11 +83,11 @@ int main(int argc, char* argv[]) } #if 1 // Search for GPMF Track - size_t mp4 = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE); + size_t mp4handle = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE); #else // look for a global GPMF payload in the moov header, within 'udta' - size_t mp4 = OpenMP4SourceUDTA(argv[1]); //Search for GPMF payload with MP4's udta + size_t mp4handle = OpenMP4SourceUDTA(argv[1]); //Search for GPMF payload with MP4's udta #endif - if (mp4 == 0) + if (mp4handle == 0) { printf("error: %s is an invalid MP4/MOV or it has no GPMF data\n\n", argv[1]); @@ -115,15 +115,15 @@ int main(int argc, char* argv[]) } - metadatalength = GetDuration(mp4); + metadatalength = GetDuration(mp4handle); if (metadatalength > 0.0) { - uint32_t index, payloads = GetNumberPayloads(mp4); + uint32_t index, payloads = GetNumberPayloads(mp4handle); // printf("found %.2fs of metadata, from %d payloads, within %s\n", metadatalength, payloads, argv[1]); uint32_t fr_num, fr_dem; - uint32_t frames = GetVideoFrameRateAndCount(mp4, &fr_num, &fr_dem); + uint32_t frames = GetVideoFrameRateAndCount(mp4handle, &fr_num, &fr_dem); if (show_video_framerate) { if (frames) @@ -135,14 +135,13 @@ int main(int argc, char* argv[]) for (index = 0; index < payloads; index++) { double in = 0.0, out = 0.0; //times - payloadsize = GetPayloadSize(mp4, index); - payloadres = GetPayloadResource(payloadres, - payloadsize); - payload = GetPayload(mp4, payloadres, index); + payloadsize = GetPayloadSize(mp4handle, index); + payloadres = GetPayloadResource(mp4handle, payloadres, payloadsize); + payload = GetPayload(mp4handle, payloadres, index); if (payload == NULL) goto cleanup; - ret = GetPayloadTime(mp4, index, &in, &out); + ret = GetPayloadTime(mp4handle, index, &in, &out); if (ret != GPMF_OK) goto cleanup; @@ -393,7 +392,7 @@ int main(int argc, char* argv[]) if (show_computed_samplerates) { mp4callbacks cbobject; - cbobject.mp4handle = mp4; + cbobject.mp4handle = mp4handle; cbobject.cbGetNumberPayloads = GetNumberPayloads; cbobject.cbGetPayload = GetPayload; cbobject.cbGetPayloadSize = GetPayloadSize; @@ -419,9 +418,9 @@ int main(int argc, char* argv[]) } cleanup: - if (payloadres) FreePayloadResource(payloadres); + if (payloadres) FreePayloadResource(mp4handle, payloadres); if (ms) GPMF_Free(ms); - CloseSource(mp4); + CloseSource(mp4handle); } if (ret != 0) diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index 9535716..8587ea2 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -39,9 +39,9 @@ #endif -uint32_t GetNumberPayloads(size_t handle) +uint32_t GetNumberPayloads(size_t mp4handle) { - mp4object *mp4 = (mp4object *)handle; + mp4object *mp4 = (mp4object *)mp4handle; if (mp4) { @@ -52,7 +52,7 @@ uint32_t GetNumberPayloads(size_t handle) } -size_t GetPayloadResource(size_t resHandle, uint32_t payloadsize) +size_t GetPayloadResource(size_t mp4handle, size_t resHandle, uint32_t payloadsize) { resObject* res = (resObject*)resHandle; @@ -100,7 +100,7 @@ size_t GetPayloadResource(size_t resHandle, uint32_t payloadsize) -void FreePayloadResource(size_t resHandle) +void FreePayloadResource(size_t mp4handle, size_t resHandle) { resObject* res = (resObject*)resHandle; @@ -112,9 +112,9 @@ void FreePayloadResource(size_t resHandle) } -uint32_t *GetPayload(size_t handle, size_t resHandle, uint32_t index) +uint32_t *GetPayload(size_t mp4handle, size_t resHandle, uint32_t index) { - mp4object *mp4 = (mp4object *)handle; + mp4object *mp4 = (mp4object *)mp4handle; resObject *res = (resObject *)resHandle; if (mp4 == NULL) return NULL; @@ -126,7 +126,7 @@ uint32_t *GetPayload(size_t handle, size_t resHandle, uint32_t index) { uint32_t buffsizeneeded = mp4->metasizes[index]; // Add a little more to limit reallocations - resHandle = GetPayloadResource(resHandle, buffsizeneeded); + resHandle = GetPayloadResource(mp4handle, resHandle, buffsizeneeded); if(resHandle) { #ifdef _WINDOWS diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h index faac0e2..40a588f 100644 --- a/demo/GPMF_mp4reader.h +++ b/demo/GPMF_mp4reader.h @@ -116,19 +116,19 @@ typedef enum MP4READER_ERROR size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t subtype); size_t OpenMP4SourceUDTA(char *filename); -void CloseSource(size_t handle); -float GetDuration(size_t handle); -uint32_t GetVideoFrameRateAndCount(size_t handle, uint32_t *numer, uint32_t *demon); -uint32_t GetNumberPayloads(size_t handle); -uint32_t WritePayload(size_t handle, uint32_t* payload, uint32_t payloadsize, uint32_t index); -size_t GetPayloadResource(size_t resHandle, uint32_t payloadBufferSize); -void FreePayloadResource(size_t resHandle); +void CloseSource(size_t mp4Handle); +float GetDuration(size_t mp4Handle); +uint32_t GetVideoFrameRateAndCount(size_t mp4Handle, uint32_t *numer, uint32_t *demon); +uint32_t GetNumberPayloads(size_t mp4Handle); +uint32_t WritePayload(size_t mp4Handle, uint32_t* payload, uint32_t payloadsize, uint32_t index); +size_t GetPayloadResource(size_t mp4Handle, size_t resHandle, uint32_t payloadBufferSize); +void FreePayloadResource(size_t mp4Handle, size_t resHandle); uint32_t* GetPayload(size_t mp4Handle, size_t resHandle, uint32_t index); uint32_t GetPayloadSize(size_t mp4Handle, uint32_t index); uint32_t GetPayloadTime(size_t mp4Handle, uint32_t index, double *in, double *out); //MP4 timestamps for the payload uint32_t GetPayloadRationalTime(size_t mp4Handle, uint32_t index, int32_t *in_numerator, int32_t *out_numerator, uint32_t *denominator); -uint32_t GetEditListOffset(size_t handle, double *offset); -uint32_t GetEditListOffsetRationalTime(size_t handle, int32_t *offset_numerator, uint32_t *denominator); +uint32_t GetEditListOffset(size_t mp4Handle, double *offset); +uint32_t GetEditListOffsetRationalTime(size_t mp4Handle, int32_t *offset_numerator, uint32_t *denominator); #ifdef __cplusplus } diff --git a/demo/GPMF_utils.c b/demo/GPMF_utils.c index 5d9ef09..d1da3b3 100644 --- a/demo/GPMF_utils.c +++ b/demo/GPMF_utils.c @@ -51,7 +51,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour return 0.0; payloadsize = cb.cbGetPayloadSize(cb.mp4handle, teststart); - payloadres = cb.cbGetPayloadResource(0, payloadsize); + payloadres = cb.cbGetPayloadResource(cb.mp4handle, 0, payloadsize); payload = cb.cbGetPayload(cb.mp4handle, payloadres, teststart); ret = GPMF_Init(ms, payload, payloadsize); @@ -351,7 +351,7 @@ double GetGPMFSampleRate(mp4callbacks cb, uint32_t fourcc, uint32_t timeBaseFour } cleanup: - if (payloadres) cb.cbFreePayloadResource(payloadres); + if (payloadres) cb.cbFreePayloadResource(cb.mp4handle, payloadres); return rate; } diff --git a/demo/GPMF_utils.h b/demo/GPMF_utils.h index 010e788..aef7534 100644 --- a/demo/GPMF_utils.h +++ b/demo/GPMF_utils.h @@ -33,14 +33,14 @@ extern "C" { typedef struct mp4callbacks { size_t mp4handle; - uint32_t (*cbGetNumberPayloads)(size_t handle); // number of indexed GPMF payloads - uint32_t (*cbGetPayloadSize)(size_t handle, uint32_t index); // get payload size for a particular index - uint32_t *(*cbGetPayload)(size_t handle, size_t res, uint32_t index); // get payload data for a particular index - size_t (*cbGetPayloadResource)(size_t reshandle, uint32_t initialMemorySize); // get payload memory handler - void (*cbFreePayloadResource)(size_t reshandle); // free payload memory handler - uint32_t (*cbGetPayloadTime)(size_t mp4Handle, uint32_t index, double* in, double* out); //MP4 timestamps for the payload - uint32_t (*cbGetEditListOffsetRationalTime)(size_t handle, // get any time offset for GPMF track - int32_t *offset_numerator, uint32_t* denominator); + uint32_t (*cbGetNumberPayloads)(size_t mp4handle); // number of indexed GPMF payloads + uint32_t (*cbGetPayloadSize)(size_t mp4handle, uint32_t index); // get payload size for a particular index + uint32_t *(*cbGetPayload)(size_t mp4handle, size_t res, uint32_t index); // get payload data for a particular index + size_t (*cbGetPayloadResource)(size_t mp4handle, size_t reshandle, uint32_t initialMemorySize); // get payload memory handler + void (*cbFreePayloadResource)(size_t mp4handle, size_t reshandle); // free payload memory handler + uint32_t (*cbGetPayloadTime)(size_t mp4handle, uint32_t index, double* in, double* out); //MP4 timestamps for the payload + uint32_t (*cbGetEditListOffsetRationalTime)(size_t mp4handle, // get any time offset for GPMF track + int32_t *offset_numerator, uint32_t* denominator); } mp4callbacks; double GetGPMFSampleRate(mp4callbacks cbobject, uint32_t fourcc, uint32_t timeBaseFourCC, uint32_t flags, double* in, double* out); From 99db337611e6e7ab2ca536ba6507ed7fd2248be1 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Wed, 27 Jan 2021 10:45:07 -0800 Subject: [PATCH 16/19] added the stsz improvements from master --- demo/GPMF_mp4reader.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c index 1fa7744..7ef5462 100644 --- a/demo/GPMF_mp4reader.c +++ b/demo/GPMF_mp4reader.c @@ -530,14 +530,15 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype, in num = BYTESWAP32(num); // if equalsamplesize != 0, it is the size of all the samples and the length should be 20 (size,fourcc,flags,samplesize,samplecount) - if ((num <= (qtsize/sizeof(uint32_t))) || (equalsamplesize != 0 && qtsize == 20)) + if (qtsize >= (20 + (num * sizeof(uint32_t))) || (equalsamplesize != 0 && qtsize == 20)) { if (mp4->metasizes) { free(mp4->metasizes); mp4->metasizes = 0; } - if(num > 0 && (qtsize > (num * 4) || (equalsamplesize != 0 && qtsize == 20))) + //either the samples are different sizes or they are all the same size + if(num > 0) { mp4->metasize_count = num; mp4->metasizes = (uint32_t *)malloc(num * 4); From 1552c0d8b216495f59b6fb949a770734591f1262 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Wed, 27 Jan 2021 11:00:32 -0800 Subject: [PATCH 17/19] fixed merge --- demo/GPMF_demo.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index d7c8f34..6dae731 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -83,15 +83,9 @@ int main(int argc, char* argv[]) } #if 1 // Search for GPMF Track -<<<<<<< HEAD - size_t mp4handle = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE); + size_t mp4handle = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE, 0); #else // look for a global GPMF payload in the moov header, within 'udta' - size_t mp4handle = OpenMP4SourceUDTA(argv[1]); //Search for GPMF payload with MP4's udta -======= - size_t mp4 = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE, 0); -#else // look for a global GPMF payload in the moov header, within 'udta' - size_t mp4 = OpenMP4SourceUDTA(argv[1], 0); //Search for GPMF payload with MP4's udta ->>>>>>> 6c2c6350e02ea1f9af399927e1520e9ffe95e312 + size_t mp4handle = OpenMP4SourceUDTA(argv[1], 0); //Search for GPMF payload with MP4's udta #endif if (mp4handle == 0) { From 8598bb5668e720b38fb44856cffe0e9d27fdafca Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 1 Mar 2021 12:42:36 -0800 Subject: [PATCH 18/19] Moved util.c/h out of the demo folder, as it is no longer demo code. --- CMakeLists.txt | 8 ++++---- demo/GPMF_utils.c => GPMF_utils.c | 3 +-- demo/GPMF_utils.h => GPMF_utils.h | 0 demo/GPMF_demo.c | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) rename demo/GPMF_utils.c => GPMF_utils.c (99%) rename demo/GPMF_utils.h => GPMF_utils.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff23669..fe92dc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ project (gpmf-parser) set(CMAKE_SUPPRESS_REGENERATION true) set(CMAKE_CONFIGURATION_TYPES "Debug;Release") -file(GLOB LIB_SOURCES "GPMF_parser.c" "demo/GPMF_mp4reader.c") -file(GLOB SOURCES ${LIB_SOURCES} "demo/GPMF_demo.c" "demo/GPMF_print.c" "demo/GPMF_utils.c") +file(GLOB LIB_SOURCES "GPMF_parser.c" "GPMF_utils.c") +file(GLOB SOURCES ${LIB_SOURCES} "demo/GPMF_demo.c" "demo/GPMF_print.c" "demo/GPMF_mp4reader.c") add_executable(GPMF_PARSER_BIN ${SOURCES}) set_target_properties(GPMF_PARSER_BIN PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") @@ -20,5 +20,5 @@ install(TARGETS GPMF_PARSER_BIN DESTINATION "bin") install(TARGETS GPMF_PARSER_LIB DESTINATION "lib") install(FILES "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION "lib/pkgconfig") -install(FILES "GPMF_parser.h" "GPMF_common.h" DESTINATION "include/gpmf-parser") -install(FILES "demo/GPMF_mp4reader.h" "demo/GPMF_utils.h" DESTINATION "include/gpmf-parser/demo") +install(FILES "GPMF_parser.h" "GPMF_common.h" "GPMF_utils.h" DESTINATION "include/gpmf-parser") +install(FILES "demo/GPMF_mp4reader.h" DESTINATION "include/gpmf-parser/demo") diff --git a/demo/GPMF_utils.c b/GPMF_utils.c similarity index 99% rename from demo/GPMF_utils.c rename to GPMF_utils.c index d1da3b3..6959a67 100644 --- a/demo/GPMF_utils.c +++ b/GPMF_utils.c @@ -24,8 +24,7 @@ #include #include -#include "../GPMF_parser.h" -#include "GPMF_mp4reader.h" +#include "GPMF_parser.h" #include "GPMF_utils.h" diff --git a/demo/GPMF_utils.h b/GPMF_utils.h similarity index 100% rename from demo/GPMF_utils.h rename to GPMF_utils.h diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c index 6dae731..82abb08 100644 --- a/demo/GPMF_demo.c +++ b/demo/GPMF_demo.c @@ -26,7 +26,7 @@ #include "../GPMF_parser.h" #include "GPMF_mp4reader.h" -#include "GPMF_utils.h" +#include "../GPMF_utils.h" #define SHOW_VIDEO_FRAMERATE 1 #define SHOW_PAYLOAD_TIME 1 From 2c14eefb7a9bf8054b70cdfac9405bcc60c7e623 Mon Sep 17 00:00:00 2001 From: dnewman-gpsw Date: Mon, 12 Apr 2021 13:38:59 -0700 Subject: [PATCH 19/19] using latest READMEs from master branch --- README.md | 25 ++++++++++++++++--------- docs/README.md | 17 ++++++++++++----- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 098c826..ada995c 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ STRM null The second data stream is almost half the size of the first (1216 vs 628 bytes) for the same resulting precision. -When adding units, the SCAL doesn't apply to SUIN, but only the ACCL the latest KLV in the stream's (STRM) nest. +When adding units, the SCAL doesn't apply to SIUN, but only the ACCL the latest KLV in the stream's (STRM) nest. ``` STRM null @@ -320,7 +320,7 @@ DEVC null As the CV processing in this example can take time, it will be common for the processing to begin before the payload frame it which it is written. The first FACE samples begin their processing at 10023, yet the payload for normal sample data began at 10140 (within the top DEVC structure). -## Standard Units for physical properties supported by SUIN +## Standard Units for physical properties supported by SIUN ### Base Units @@ -424,11 +424,11 @@ TYPE 'c' 1 5 "f[8]L" ## Sticky Metadata -The metadata writing API should have a mechanism for creating and writing "sticky" metadata. Data of type sticky doesn't need to be re-transmitted if it is not changing, and it will only store the last value it is does change. Sticky data is good for slow changing properties like the sensor's temperature, or any data that is sampled near the payload frequency or slower. Any metadata the modifies the meaning of sensor data should be written as sticky data: all TYPEs, SUINs, SCALs, naming fields (DVNM, STNM) and comments that are not changing over time. This data needs to be within each payload in the event that the file is cut in to smaller segments, each segment must contain all relevant metadata so that no meaning is lost in the extracted clip. +The metadata writing API should have a mechanism for creating and writing "sticky" metadata. Data of type sticky doesn't need to be re-transmitted if it is not changing, and it will only store the last value it is does change. Sticky data is good for slow changing properties like the sensor's temperature, or any data that is sampled near the payload frequency or slower. Any metadata the modifies the meaning of sensor data should be written as sticky data: all TYPEs, SIUNs, SCALs, naming fields (DVNM, STNM) and comments that are not changing over time. This data needs to be within each payload in the event that the file is cut in to smaller segments, each segment must contain all relevant metadata so that no meaning is lost in the extracted clip. ## Style Guide -The addition of structure is not to make device vendor's life more difficult, but to communicate the meaning of data to a future reader, that may not even be aware of the device used in the original capture. The first useful metadata is a human readable name of the device. While DVNM (DeviceName) "Camera" is in the current metadata, saying "GoPro Hero5 Black" would be much better. Having a stream (STRM) with ACCL data, doesn't communicate what it contains, when adding a STNM or RMRK with "Accelerometer (up/down, right/left, forward/back)" adds a lot of clarity to the future data reader. SUIN, UNIT, SCAL and even TYPE is completely optional if you don't intend anyone other than for your own tools to process this data, but it is so much better to allow many tools to extract and process all data. Use of a standard unit (SIUN) allows downstream tools to convert accurately from m/s to MPH or kmH etc. under the end users control. +The addition of structure is not to make device vendor's life more difficult, but to communicate the meaning of data to a future reader, that may not even be aware of the device used in the original capture. The first useful metadata is a human readable name of the device. While DVNM (DeviceName) "Camera" is in the current metadata, saying "GoPro Hero5 Black" would be much better. Having a stream (STRM) with ACCL data, doesn't communicate what it contains, when adding a STNM or RMRK with "Accelerometer (up/down, right/left, forward/back)" adds a lot of clarity to the future data reader. SIUN, UNIT, SCAL and even TYPE is completely optional if you don't intend anyone other than for your own tools to process this data, but it is so much better to allow many tools to extract and process all data. Use of a standard unit (SIUN) allows downstream tools to convert accurately from m/s to MPH or kmH etc. under the end users control. ## MP4 Implementation @@ -560,22 +560,29 @@ For more information of GPSP (or DOP) see https://en.wikipedia.org/wiki/Dilution | FourCC | Property | approximate frequency (Hz) | SIUN or UNIT | Comment | | --- | --- | --- | --- | --- | -| CORI | Camera ORIentation | frame rate | n/a | Quaterions for the camera orientation since capture start | -| IORI | Image ORIentation | frame rate | n/a | Quaterions for the image orientation relative to the camera body | +| CORI | Camera ORIentation | frame rate | n/a | Quaternions for the camera orientation since capture start | +| IORI | Image ORIentation | frame rate | n/a | Quaternions for the image orientation relative to the camera body | | GRAV | GRAvity Vector | frame rate | n/a | Vector for the direction for gravitiy | | WNDM | Wind Processing | 10Hz | n/a | marks whether wind processing is active | | MWET | Microphone is WET | 10Hz | n/a | marks whether some of the microphones are wet | | AALP | Audio Levels | 10Hz | dBFS | RMS and peak audio levels in dBFS | -### GoPro MAX (v1.3) Adds, Removes, Changes, Otherwise Supports All HERO7 metadata +### GoPro MAX (v2.0) Adds, Removes, Changes, Otherwise Supports All HERO7 metadata | FourCC | Property | approximate frequency (Hz) | SIUN or UNIT | Comment | | --- | --- | --- | --- | --- | -| CORI | Camera ORIentation | frame rate | n/a | Quaterions for the camera orientation since capture start | -| IORI | Image ORIentation | frame rate | n/a | Quaterions for the image orientation relative to the camera body | +| CORI | Camera ORIentation | frame rate | n/a | Quaternions for the camera orientation since capture start | +| IORI | Image ORIentation | frame rate | n/a | Quaternions for the image orientation relative to the camera body | | GRAV | GRAvity Vector | frame rate | n/a | Vector for the direction for gravity | | DISP | Disparity track (360 modes) | frame rate | n/a | 1-D depth map for the objects seen by the two lenses | +| MAGN | MAGNnetometer | 24 | µT | Camera pointing direction x,y,z (valid in v2.0 firmware.) | +### HERO9 Black (v1.5) Adds, Removes, Changes, Otherwise Supports All HERO8 metadata + +| FourCC | Property | approximate frequency (Hz) | SIUN or UNIT | Comment | +| --- | --- | --- | --- | --- | +| MSKP | Main video frame SKiP | frame rate | n/a | GoPro internal usage. Number frames skips or duplicated from sensor image captured to encoded frame. Normally 0. This is used for visual effects when precision timing of the video frame is required. | +| LSKP | Low res video frame SKiP | frame rate | n/a | GoPro internal usage. Same as MSKP for the LRV video file (when present.) This improves sync with the main video when using the LRV as a proxy. | ``` GoPro is trademark of GoPro, Inc. diff --git a/docs/README.md b/docs/README.md index 1d80ad8..b4e7391 100644 --- a/docs/README.md +++ b/docs/README.md @@ -240,7 +240,7 @@ STRM null The second data stream is almost half the size of the first (1216 vs 628 bytes) for the same resulting precision. -When adding units, the SCAL doesn't apply to SUIN, but only the ACCL the latest KLV in the stream's (STRM) nest. +When adding units, the SCAL doesn't apply to SIUN, but only the ACCL the latest KLV in the stream's (STRM) nest. ``` STRM null @@ -320,7 +320,7 @@ DEVC null As the CV processing in this example can take time, it will be common for the processing to begin before the payload frame it which it is written. The first FACE samples begin their processing at 10023, yet the payload for normal sample data began at 10140 (within the top DEVC structure). -## Standard Units for physical properties supported by SUIN +## Standard Units for physical properties supported by SIUN ### Base Units @@ -424,11 +424,11 @@ TYPE 'c' 1 5 "f[8]L" ## Sticky Metadata -The metadata writing API should have a mechanism for creating and writing "sticky" metadata. Data of type sticky doesn't need to be re-transmitted if it is not changing, and it will only store the last value it is does change. Sticky data is good for slow changing properties like the sensor's temperature, or any data that is sampled near the payload frequency or slower. Any metadata the modifies the meaning of sensor data should be written as sticky data: all TYPEs, SUINs, SCALs, naming fields (DVNM, STNM) and comments that are not changing over time. This data needs to be within each payload in the event that the file is cut in to smaller segments, each segment must contain all relevant metadata so that no meaning is lost in the extracted clip. +The metadata writing API should have a mechanism for creating and writing "sticky" metadata. Data of type sticky doesn't need to be re-transmitted if it is not changing, and it will only store the last value it is does change. Sticky data is good for slow changing properties like the sensor's temperature, or any data that is sampled near the payload frequency or slower. Any metadata the modifies the meaning of sensor data should be written as sticky data: all TYPEs, SIUNs, SCALs, naming fields (DVNM, STNM) and comments that are not changing over time. This data needs to be within each payload in the event that the file is cut in to smaller segments, each segment must contain all relevant metadata so that no meaning is lost in the extracted clip. ## Style Guide -The addition of structure is not to make device vendor's life more difficult, but to communicate the meaning of data to a future reader, that may not even be aware of the device used in the original capture. The first useful metadata is a human readable name of the device. While DVNM (DeviceName) "Camera" is in the current metadata, saying "GoPro Hero5 Black" would be much better. Having a stream (STRM) with ACCL data, doesn't communicate what it contains, when adding a STNM or RMRK with "Accelerometer (up/down, right/left, forward/back)" adds a lot of clarity to the future data reader. SUIN, UNIT, SCAL and even TYPE is completely optional if you don't intend anyone other than for your own tools to process this data, but it is so much better to allow many tools to extract and process all data. Use of a standard unit (SIUN) allows downstream tools to convert accurately from m/s to MPH or kmH etc. under the end users control. +The addition of structure is not to make device vendor's life more difficult, but to communicate the meaning of data to a future reader, that may not even be aware of the device used in the original capture. The first useful metadata is a human readable name of the device. While DVNM (DeviceName) "Camera" is in the current metadata, saying "GoPro Hero5 Black" would be much better. Having a stream (STRM) with ACCL data, doesn't communicate what it contains, when adding a STNM or RMRK with "Accelerometer (up/down, right/left, forward/back)" adds a lot of clarity to the future data reader. SIUN, UNIT, SCAL and even TYPE is completely optional if you don't intend anyone other than for your own tools to process this data, but it is so much better to allow many tools to extract and process all data. Use of a standard unit (SIUN) allows downstream tools to convert accurately from m/s to MPH or kmH etc. under the end users control. ## MP4 Implementation @@ -567,7 +567,7 @@ For more information of GPSP (or DOP) see https://en.wikipedia.org/wiki/Dilution | MWET | Microphone is WET | 10Hz | n/a | marks whether some of the microphones are wet | | AALP | Audio Levels | 10Hz | dBFS | RMS and peak audio levels in dBFS | -### GoPro MAX (v1.3) Adds, Removes, Changes, Otherwise Supports All HERO7 metadata +### GoPro MAX (v2.0) Adds, Removes, Changes, Otherwise Supports All HERO7 metadata | FourCC | Property | approximate frequency (Hz) | SIUN or UNIT | Comment | | --- | --- | --- | --- | --- | @@ -575,7 +575,14 @@ For more information of GPSP (or DOP) see https://en.wikipedia.org/wiki/Dilution | IORI | Image ORIentation | frame rate | n/a | Quaterions for the image orientation relative to the camera body | | GRAV | GRAvity Vector | frame rate | n/a | Vector for the direction for gravity | | DISP | Disparity track (360 modes) | frame rate | n/a | 1-D depth map for the objects seen by the two lenses | +| MAGN | MAGNnetometer | 24 | µT | Camera pointing direction x,y,z (valid in v2.0 firmware.) | +### HERO9 Black (v1.5) Adds, Removes, Changes, Otherwise Supports All HERO8 metadata + +| FourCC | Property | approximate frequency (Hz) | SIUN or UNIT | Comment | +| --- | --- | --- | --- | --- | +| MSKP | Main video frame SKiP | frame rate | n/a | GoPro internal usage. Number frames skips or duplicated from sensor image captured to encoded frame. Normally 0. This is used for visual effects when precision timing of the video frame is required. | +| LSKP | Low res video frame SKiP | frame rate | n/a | GoPro internal usage. Same as MSKP for the LRV video file (when present.) This improves sync with the main video when using the LRV as a proxy. | ``` GoPro is trademark of GoPro, Inc.