CVE-2019-15146: Out-of-bounds Read (CWE-125)
value: GoPro GPMF-parser 1.2.2 has a heap-based buffer over-read (4 bytes) in GPMF_Next in GPMF_parser.c.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 6.5
Published: 2019-08-18T19:15Z
Repository: https://github.com/gopro/gpmf-parser
Language: C
Files changed: GPMF_parser.c, GPMF_parser.h, demo/GPMF_demo.c, demo/GPMF_mp4reader.c, demo/GPMF_mp4reader.h
Commit date: 2019-05-28 16:29:02 -0700
Commit message: fixed many security issues with the too crude mp4 reader

Diff: diff --git a/GPMF_parser.c b/GPMF_parser.c
index 5c05c91..aacced9 100644
--- a/GPMF_parser.c
+++ b/GPMF_parser.c
@@ -1,10 +1,10 @@
 /*! @file GPMF_parser.c
  * 
  *  @brief GPMF Parser library
  *
- *  @version 1.2.1
+ *  @version 1.2.2
  * 
  *  (C) Copyright 2017 GoPro Inc (http://gopro.com/).
  *	
  *  Licensed under either:
  *  - Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0  
@@ -40,11 +40,11 @@
 
 GPMF_ERR IsValidSize(GPMF_stream *ms, uint32_t size) // size is in longs not bytes.
 {
 	if (ms)
 	{
-		int32_t nestsize = (int32_t)ms->nest_size[ms->nest_level];
+		uint32_t nestsize = (uint32_t)ms->nest_size[ms->nest_level];
 		if (nestsize == 0 && ms->nest_level == 0)
 			nestsize = ms->buffer_size_longs;
 
 		if (size + 2 <= nestsize) return GPMF_OK;
 	}
diff --git a/GPMF_parser.h b/GPMF_parser.h
index 101958b..63a786a 100644
--- a/GPMF_parser.h
+++ b/GPMF_parser.h
@@ -1,10 +1,10 @@
 /*! @file GPMF_parser.h
  * 
  *  @brief GPMF Parser library include
  * 
- *  @version 1.1.0
+ *  @version 1.1.1
  * 
  *  (C) Copyright 2017 GoPro Inc (http://gopro.com/).
  *	
  *  Licensed under either:
  *  - Apache License, Version 2.0, http://www.apache.org/licenses/LICENSE-2.0  
@@ -124,11 +124,15 @@ typedef enum GPMFKey // TAG in all caps are GoPro preserved (are defined by GoPr
 	GPMF_KEY_STREAM_NAME =		MAKEID('S','T','N','M'),//STNM - human readable telemetry/metadata stream type/name (char string)
 	GPMF_KEY_SI_UNITS =			MAKEID('S','I','U','N'),//SIUN - Display string for metadata units where inputs are in SI units "uT","rad/s","km/s","m/s","mm/s" etc.
 	GPMF_KEY_UNITS =			MAKEID('U','N','I','T'),//UNIT - Freedform display string for metadata units (char sting like "RPM", "MPH", "km/h", etc)
 	GPMF_KEY_SCALE =			MAKEID('S','C','A','L'),//SCAL - divisor for input data to scale to the correct units.
 	GPMF_KEY_TYPE =				MAKEID('T','Y','P','E'),//TYPE - Type define for complex data structures
-	GPMF_KEY_TOTAL_SAMPLES =	MAKEID('T','S','M','P'),//TOTL - Total Sample Count including the current payload 	
+	GPMF_KEY_TOTAL_SAMPLES =	MAKEID('T','S','M','P'),//TSMP - Total Sample Count including the current payload 	
+	GPMF_KEY_TIME_OFFSET =		MAKEID('T','I','M','O'),//TIMO - Time offset of the metadata stream that follows (single 4 byte float)
+	GPMF_KEY_TIMING_OFFSET =	MAKEID('T','I','M','O'),//TIMO - duplicated, as older code might use the other version of TIMO
+	GPMF_KEY_TIME_STAMP =		MAKEID('S','T','M','P'),//STMP - Time stamp for the first sample. 
+	GPMF_KEY_TIME_STAMPS =		MAKEID('S','T','P','S'),//STPS - Stream of all the timestamps delivered (Generally don't use this. This would be if your sensor has no peroidic times, yet precision is required, or for debugging.) 
 	GPMF_KEY_TICK =				MAKEID('T','I','C','K'),//TICK - Used for slow data. Beginning of data timing in milliseconds. 
 	GPMF_KEY_TOCK =				MAKEID('T','O','C','K'),//TOCK - Used for slow data. End of data timing in milliseconds. 
 	GPMF_KEY_EMPTY_PAYLOADS =	MAKEID('E','M','P','T'),//EMPT - Payloads that are empty since the device start (e.g. BLE disconnect.)
 	GPMF_KEY_REMARK =			MAKEID('R','M','R','K'),//RMRK - addcing comments to the bitstream (debugging)
 
diff --git a/demo/GPMF_demo.c b/demo/GPMF_demo.c
index df75065..dde13b4 100644
--- a/demo/GPMF_demo.c
+++ b/demo/GPMF_demo.c
@@ -44,10 +44,16 @@ int main(int argc, char *argv[])
 		printf("usage: %s <file_with_GPMF>\n", argv[0]);
 		return -1;
 	}
 
 	size_t mp4 = OpenMP4Source(argv[1], MOV_GPMF_TRAK_TYPE, MOV_GPMF_TRAK_SUBTYPE);
+	if (mp4 == 0)
+	{
+		printf("error: %s is an invalid MP4/MOV\n", argv[1]);
+		return -1;
+	}
+
 //	size_t mp4 = OpenMP4SourceUDTA(argv[1]);  //Search for GPMF payload with MP4's udta 
 
 	metadatalength = GetDuration(mp4);
 
 	if (metadatalength > 0.0)
@@ -88,11 +94,11 @@ int main(int argc, char *argv[])
 
 
 		for (index = 0; index < payloads; index++)
 		{
 			uint32_t payloadsize = GetPayloadSize(mp4, index);
-			float in = 0.0, out = 0.0; //times
+			double in = 0.0, out = 0.0; //times
 			payload = GetPayload(mp4, payload, index);
 			if (payload == NULL)
 				goto cleanup;
 
 			ret = GetPayloadTime(mp4, index, &in, &out);
@@ -236,13 +242,14 @@ int main(int argc, char *argv[])
 		// Find all the available Streams and compute they sample rates
 		while (GPMF_OK == GPMF_FindNext(ms, GPMF_KEY_STREAM, GPMF_RECURSE_LEVELS))
 		{
 			if (GPMF_OK == GPMF_SeekToSamples(ms)) //find the last FOURCC within the stream
 			{
+				double in = 0.0, out = 0.0;
 				uint32_t fourcc = GPMF_Key(ms);
-				double rate = GetGPMFSampleRate(mp4, fourcc, GPMF_SAMPLE_RATE_PRECISE);// GPMF_SAMPLE_RATE_FAST);
-				printf("%c%c%c%c sampling rate = %f Hz\n", PRINTF_4CC(fourcc), rate);
+				double rate = GetGPMFSampleRate(mp4, fourcc, GPMF_SAMPLE_RATE_PRECISE, &in, &out);// GPMF_SAMPLE_RATE_FAST);
+				printf("%c%c%c%c sampling rate = %f Hz (from %f to %f)\n", PRINTF_4CC(fourcc), rate, in, out);
 			}
 		}
 #endif
 
 
diff --git a/demo/GPMF_mp4reader.c b/demo/GPMF_mp4reader.c
index 2977167..1b6d25f 100644
--- a/demo/GPMF_mp4reader.c
+++ b/demo/GPMF_mp4reader.c
@@ -1,42 +1,44 @@
 /*! @file mp4reader.c
 *
 *  @brief Way Too Crude MP4|MOV reader
 *
-*  @version 1.2.1
+*  @version 1.3.1
 *
-*  (C) Copyright 2017 GoPro Inc (http://gopro.com/).
+*  (C) Copyright 2017-2019 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
+*  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.
 *
 */
 
-/* This is not an elegant MP4 parser, only used to help demonstrate extraction of MP4 */
-
+/* This is not an elegant MP4 parser, only used to help demonstrate extraction of GPMF */
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
 #include "GPMF_mp4reader.h"
 
 #define PRINT_MP4_STRUCTURE		0
 
 #ifdef WIN32
-#define LONGSEEK  _fseeki64
+#define LONGSEEK	_fseeki64
+#define stat64		_stat64
 #else
-#define LONGSEEK  fseeko
+#define LONGSEEK	fseeko
 #endif
 
 
 uint32_t GetNumberPayloads(size_t handle)
 {
@@ -48,11 +50,10 @@ uint32_t GetNumberPayloads(size_t handle)
 	}
 
 	return 0;
 }
 
-
 uint32_t *GetPayload(size_t handle, uint32_t *lastpayload, uint32_t index)
 {
 	mp4object *mp4 = (mp4object *)handle;
 	if (mp4 == NULL) return NULL;
 
@@ -61,35 +62,39 @@ uint32_t *GetPayload(size_t handle, uint32_t *lastpayload, uint32_t index)
 	{
 		MP4buffer = (uint32_t *)realloc((void *)lastpayload, mp4->metasizes[index]);
 
 		if (MP4buffer)
 		{
-			LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
-			fread(MP4buffer, 1, mp4->metasizes[index], mp4->mediafp);
-			return MP4buffer;
+			if (mp4->filesize > mp4->metaoffsets[index]+mp4->metasizes[index])
+			{
+				LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
+				fread(MP4buffer, 1, mp4->metasizes[index], mp4->mediafp);
+				mp4->filepos = mp4->metaoffsets[index] + mp4->metasizes[index];
+				return MP4buffer;
+			}
 		}
 	}
 	return NULL;
 }
 
 
-void SavePayload(size_t handle, uint32_t *payload, uint32_t index)
+void LongSeek(mp4object *mp4, int64_t offset)
 {
-	mp4object *mp4 = (mp4object *)handle;
-	if (mp4 == NULL) return;
-
-	uint32_t *MP4buffer = NULL;
-	if (index < mp4->indexcount && mp4->mediafp && payload)
+	if (mp4 && offset)
 	{
-		LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
-		fwrite(payload, 1, mp4->metasizes[index], mp4->mediafp);
+		if (mp4->filepos + offset < mp4->filesize)
+		{
+			LONGSEEK(mp4->mediafp, offset, SEEK_CUR);
+			mp4->filepos += offset;
+		}
+		else
+		{
+			mp4->filepos = mp4->filesize;
+		}
 	}
-	return;
 }
 
-
-
 void FreePayload(uint32_t *lastpayload)
 {
 	if (lastpayload)
 		free(lastpayload);
 }
@@ -104,19 +109,26 @@ uint32_t GetPayloadSize(size_t handle, uint32_t index)
 		return mp4->metasizes[index];
 
 	return 0;
 }
 
+
 #define MAX_NEST_LEVEL	20
 
 size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  //RAW or within MP4
 {
 	mp4object *mp4 = (mp4object *)malloc(sizeof(mp4object));
 	if (mp4 == NULL) return 0;
 
 	memset(mp4, 0, sizeof(mp4object));
 
+	struct stat64 mp4stat;
+	stat64(filename, &mp4stat);
+	mp4->filesize = mp4stat.st_size;
+
+	if (mp4->filesize < 64) return 0;
+
 #ifdef _WINDOWS
 	fopen_s(&mp4->mediafp, filename, "rb");
 #else
 	mp4->mediafp = fopen(filename, "rb");
 #endif
@@ -127,29 +139,31 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 		size_t len;
 		int32_t nest = 0;
 		uint64_t nestsize[MAX_NEST_LEVEL] = { 0 };
 		uint64_t lastsize = 0, qtsize;
 
+
 		do
 		{
 			len = fread(&qtsize32, 1, 4, mp4->mediafp);
 			len += fread(&qttag, 1, 4, mp4->mediafp);
-			if (len == 8)
+			mp4->filepos += len;
+			if (len == 8 && mp4->filepos < mp4->filesize)
 			{
 				if (!VALID_FOURCC(qttag))
 				{
-					LONGSEEK(mp4->mediafp, lastsize - 8 - 8, SEEK_CUR);
-
-					NESTSIZE(lastsize - 8);
-					continue;
+					CloseSource((size_t)mp4);
+					mp4 = NULL;
+					break;
 				}
 
 				qtsize32 = BYTESWAP32(qtsize32);
 
 				if (qtsize32 == 1) // 64-bit Atom
 				{
-					fread(&qtsize, 1, 8, mp4->mediafp);
+					len = fread(&qtsize, 1, 8, mp4->mediafp);
+					mp4->filepos += len;
 					qtsize = BYTESWAP64(qtsize) - 8;
 				}
 				else
 					qtsize = qtsize32;
 
@@ -166,13 +180,14 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 				for (int i = 1; i < nest; i++) printf("    ");
 				printf("%c%c%c%c (%lld)\n", (qttag & 0xff), ((qttag >> 8) & 0xff), ((qttag >> 16) & 0xff), ((qttag >> 24) & 0xff), qtsize);
 
 				if (qttag == MAKEID('m', 'd', 'a', 't') ||
 					qttag == MAKEID('f', 't', 'y', 'p') ||
-					qttag == MAKEID('u', 'd', 't', 'a'))
+					qttag == MAKEID('u', 'd', 't', 'a') ||
+					qttag == MAKEID('f', 'r', 'e', 'e'))
 				{
-					LONGSEEK(mediafp, qtsize - 8, SEEK_CUR);
+					LongSeek(mp4, qtsize - 8);
 
 					NESTSIZE(qtsize);
 
 					continue;
 				}
@@ -185,21 +200,19 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 					qttag != MAKEID('m', 'i', 'n', 'f') &&
 					qttag != MAKEID('g', 'm', 'i', 'n') &&
 					qttag != MAKEID('d', 'i', 'n', 'f') &&
 					qttag != MAKEID('a', 'l', 'i', 's') &&
 					qttag != MAKEID('s', 't', 's', 'd') &&
-					qttag != MAKEID('a', 'l', 'i', 's') &&
-					qttag != MAKEID('a', 'l', 'i', 's') &&
 					qttag != MAKEID('s', 't', 'b', 'l') &&
 					qttag != MAKEID('s', 't', 't', 's') &&
 					qttag != MAKEID('s', 't', 's', 'c') &&
 					qttag != MAKEID('s', 't', 's', 'z') &&
 					qttag != MAKEID('s', 't', 'c', 'o') &&
 					qttag != MAKEID('c', 'o', '6', '4') &&
 					qttag != MAKEID('h', 'd', 'l', 'r'))
 				{
-					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+					LongSeek(mp4, qtsize - 8);
 
 					NESTSIZE(qtsize);
 				}
 				else
 #endif
@@ -208,11 +221,13 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 						len = fread(&skip, 1, 4, mp4->mediafp);
 						len += fread(&skip, 1, 4, mp4->mediafp);
 						len += fread(&skip, 1, 4, mp4->mediafp);
 						len += fread(&mp4->clockdemon, 1, 4, mp4->mediafp); mp4->clockdemon = BYTESWAP32(mp4->clockdemon);
 						len += fread(&mp4->clockcount, 1, 4, mp4->mediafp); mp4->clockcount = BYTESWAP32(mp4->clockcount);
-						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over mvhd
+
+						mp4->filepos += len;
+						LongSeek(mp4, qtsize - 8 - len); // skip over mvhd
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('m', 'd', 'h', 'd')) //mdhd  media header
 					{
@@ -231,25 +246,28 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 							if (mp4->videolength == 0.0) // Get the video length from the first track
 							{
 								mp4->videolength = (float)((double)mp4->trak_clockcount / (double)mp4->trak_clockdemon);
 							}
 						}
-						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over mvhd
+
+						mp4->filepos += len;
+						LongSeek(mp4, qtsize - 8 - len); // skip over mvhd
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('h', 'd', 'l', 'r')) //hldr
 					{
 						uint32_t temp;
 						len = fread(&skip, 1, 4, mp4->mediafp);
 						len += fread(&skip, 1, 4, mp4->mediafp);
 						len += fread(&temp, 1, 4, mp4->mediafp);  // type will be 'meta' for the correct trak.
 
-						if (temp != MAKEID('a', 'l', 'i', 's'))
+						if (temp != MAKEID('a', 'l', 'i', 's') && temp != MAKEID('u', 'r', 'l', ' '))
 							type = temp;
 
-						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over hldr
+						mp4->filepos += len;
+						LongSeek(mp4, qtsize - 8 - len); // skip over hldr
 
 						NESTSIZE(qtsize);
 
 					}
 					else if (qttag == MAKEID('s', 't', 's', 'd')) //read the sample decription to determine the type of metadata
@@ -265,14 +283,15 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 								if (subtype != traksubtype) // MP4 metadata 
 								{
 									type = 0; // MP4
 								}
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsd
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stsd
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('s', 't', 's', 'c')) // metadata stsc - offset chunks
 					{
@@ -284,36 +303,39 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 							num = BYTESWAP32(num);
 							if (num * 12 <= qtsize - 8 - len)
 							{
 								mp4->metastsc_count = num;
 								if (mp4->metastsc) free(mp4->metastsc);
-								mp4->metastsc = (SampleToChunk *)malloc(num * 12);
-								if (mp4->metastsc)
+								if (num > 0)
 								{
-									uint32_t total_stsc = num;
-									len += fread(mp4->metastsc, 1, num * sizeof(SampleToChunk), mp4->mediafp);
-
-									do
+									mp4->metastsc = (SampleToChunk *)malloc(num * sizeof(SampleToChunk));
+									if (mp4->metastsc)
 									{
-										num--;
-										mp4->metastsc[num].chunk_num = BYTESWAP32(mp4->metastsc[num].chunk_num);
-										mp4->metastsc[num].samples = BYTESWAP32(mp4->metastsc[num].samples);
-										mp4->metastsc[num].id = BYTESWAP32(mp4->metastsc[num].id);
-									} while (num > 0);
-								}
+										len += fread(mp4->metastsc, 1, num * sizeof(SampleToChunk), mp4->mediafp);
 
-								if (mp4->metastsc_count == 1 && mp4->metastsc[0].samples == 1) // Simplify if the stsc is not reporting any grouped chunks.
+										do
+										{
+											num--;
+											mp4->metastsc[num].chunk_num = BYTESWAP32(mp4->metastsc[num].chunk_num);
+											mp4->metastsc[num].samples = BYTESWAP32(mp4->metastsc[num].samples);
+											mp4->metastsc[num].id = BYTESWAP32(mp4->metastsc[num].id);
+										} while (num > 0);
+									}
+								}
+								else
 								{
-									if (mp4->metastsc) free(mp4->metastsc);
-									mp4->metastsc = NULL;
-									mp4->metastsc_count = 0;
+									//size of null
+									CloseSource((size_t)mp4);
+									mp4 = NULL;
+									break;
 								}
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsx
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stsx
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('s', 't', 's', 'z')) // metadata stsz - sizes
 					{
@@ -328,37 +350,48 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 							num = BYTESWAP32(num);
 							if (num * 4 <= qtsize - 8 - len)
 							{
 								mp4->metasize_count = num;
 								if (mp4->metasizes) free(mp4->metasizes);
-								mp4->metasizes = (uint32_t *)malloc(num * 4);
-								if (mp4->metasizes)
+								if(num > 0)
 								{
-									if (equalsamplesize == 0)
+									mp4->metasizes = (uint32_t *)malloc(num * 4);
+									if (mp4->metasizes)
 									{
-										len += fread(mp4->metasizes, 1, num * 4, mp4->mediafp);
-										do
+										if (equalsamplesize == 0)
 										{
-											num--;
-											mp4->metasizes[num] = BYTESWAP32(mp4->metasizes[num]);
-										} while (num > 0);
-									}
-									else
-									{
-										equalsamplesize = BYTESWAP32(equalsamplesize);
-										do
+											len += fread(mp4->metasizes, 1, num * 4, mp4->mediafp);
+											do
+											{
+												num--;
+												mp4->metasizes[num] = BYTESWAP32(mp4->metasizes[num]);
+											} while (num > 0);
+										}
+										else
 										{
-											num--;
-											mp4->metasizes[num] = equalsamplesize;
-										} while (num > 0);
+											equalsamplesize = BYTESWAP32(equalsamplesize);
+											do
+											{
+												num--;
+												mp4->metasizes[num] = equalsamplesize;
+											} while (num > 0);
+										}
 									}
 								}
+								else
+								{
+									//size of null
+									CloseSource((size_t)mp4);
+									mp4 = NULL;
+									break;
+								}
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsz
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stsz
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('s', 't', 'c', 'o')) // metadata stco - offsets
 					{
@@ -367,97 +400,125 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 							len = fread(&skip, 1, 4, mp4->mediafp);
 							len += fread(&num, 1, 4, mp4->mediafp);
 							num = BYTESWAP32(num);
 							if (num * 4 <= qtsize - 8 - len)
 							{
+								uint32_t metastco_count = num;
+
 								if (mp4->metastsc_count > 0 && num != mp4->metasize_count)
 								{
-									mp4->indexcount = mp4->metasize_count;
+									mp4->indexcount = num;
 									if (mp4->metaoffsets) free(mp4->metaoffsets);
-									mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
-									if (mp4->metaoffsets)
+									if(num > 0)
 									{
-										uint32_t *metaoffsets32 = NULL;
-										metaoffsets32 = (uint32_t *)malloc(num * 4);
-										if (metaoffsets32)
+										mp4->metaoffsets = (uint64_t *)malloc(num * 8);
+										if (mp4->metaoffsets)
 										{
-											uint64_t fileoffset = 0;
-											int stsc_pos = 0;
-											int stco_pos = 0;
-											int repeat = 1;
-											len += fread(metaoffsets32, 1, num * 4, mp4->mediafp);
-											do
-											{
-												num--;
-												metaoffsets32[num] = BYTESWAP32(metaoffsets32[num]);
-											} while (num > 0);
-
-											mp4->metaoffsets[0] = fileoffset = metaoffsets32[stco_pos];
-											num = 1;
-											while (num < mp4->metasize_count)
+											uint32_t *metaoffsets32 = NULL;
+											metaoffsets32 = (uint32_t *)malloc(num * 4);
+											if (metaoffsets32)
 											{
-												if (stsc_pos + 1 < (int)mp4->metastsc_count && num == stsc_pos)
+												uint64_t fileoffset = 0;
+												int stsc_pos = 0;
+												int stco_pos = 0;
+												int repeat = 1;
+												len += fread(metaoffsets32, 1, num * 4, mp4->mediafp);
+												do
 												{
-													stco_pos++; stsc_pos++;
-													fileoffset = (uint64_t)metaoffsets32[stco_pos];
-													repeat = 1;
-												}
-												else if (repeat == mp4->metastsc[stsc_pos].samples)
-												{
-													stco_pos++;
-													fileoffset = (uint64_t)metaoffsets32[stco_pos];
-													repeat = 1;
-												}
-												else
+													num--;
+													metaoffsets32[num] = BYTESWAP32(metaoffsets32[num]);
+												} while (num > 0);
+
+												mp4->metaoffsets[0] = fileoffset = metaoffsets32[stco_pos];
+												num = 1;
+												while (num < mp4->indexcount)
 												{
-													fileoffset += (uint64_t)mp4->metasizes[num - 1];
-													repeat++;
+													if ((uint32_t)repeat == mp4->metastsc[stsc_pos].samples)
+													{
+														if ((uint32_t)stco_pos + 1 < metastco_count)
+														{
+															stco_pos++;
+															fileoffset = (uint64_t)metaoffsets32[stco_pos];
+														}
+														else
+														{
+															fileoffset += (uint64_t)mp4->metasizes[num - 1];
+														}
+														if ((uint32_t)stsc_pos + 1 < mp4->metastsc_count)
+															if (mp4->metastsc[stsc_pos + 1].chunk_num == (uint32_t)stco_pos + 1)
+																stsc_pos++;
+
+														repeat = 1;
+													}
+													else
+													{
+														fileoffset += (uint64_t)mp4->metasizes[num - 1];
+														repeat++;
+													}
+
+													mp4->metaoffsets[num] = fileoffset;
+													//int delta = metaoffsets[num] - metaoffsets[num - 1];
+													//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
+
+													num++;
 												}
 
-												mp4->metaoffsets[num] = fileoffset;
-												//int delta = metaoffsets[num] - metaoffsets[num - 1];
-												//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
+												if (mp4->metastsc) free(mp4->metastsc);
+												mp4->metastsc = NULL;
+												mp4->metastsc_count = 0;
 
-												num++;
+												free(metaoffsets32);
 											}
-
-											if (mp4->metastsc) free(mp4->metastsc);
-											mp4->metastsc = NULL;
-											mp4->metastsc_count = 0;
-
-											free(metaoffsets32);
 										}
 									}
+									else
+									{
+										//size of null
+										CloseSource((size_t)mp4);
+										mp4 = NULL;
+										break;
+									}
 								}
 								else
 								{
 									mp4->indexcount = num;
 									if (mp4->metaoffsets) free(mp4->metaoffsets);
-									mp4->metaoffsets = (uint64_t *)malloc(num * 8);
-									if (mp4->metaoffsets)
+									if (num > 0)
 									{
-										uint32_t *metaoffsets32 = NULL;
-										metaoffsets32 = (uint32_t *)malloc(num * 4);
-										if (metaoffsets32)
+										mp4->metaoffsets = (uint64_t *)malloc(num * 8);
+										if (mp4->metaoffsets)
 										{
-											size_t readlen = fread(metaoffsets32, 1, num * 4, mp4->mediafp);
-											len += readlen;
-											do
+											uint32_t *metaoffsets32 = NULL;
+											metaoffsets32 = (uint32_t *)malloc(num * 4);
+											if (metaoffsets32)
 											{
-												num--;
-												mp4->metaoffsets[num] = BYTESWAP32(metaoffsets32[num]);
-											} while (num > 0);
+												size_t readlen = fread(metaoffsets32, 1, num * 4, mp4->mediafp);
+												len += readlen;
+												do
+												{
+													num--;
+													mp4->metaoffsets[num] = BYTESWAP32(metaoffsets32[num]);
+												} while (num > 0);
 
-											free(metaoffsets32);
+												free(metaoffsets32);
+											}
 										}
 									}
+									else
+									{
+										//size of null
+										CloseSource((size_t)mp4);
+										mp4 = NULL;
+										break;
+									}
 								}
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stco
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 
 					else if (qttag == MAKEID('c', 'o', '6', '4')) // metadata stco - offsets
@@ -465,64 +526,83 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 						if (type == traktype) // meta
 						{
 							len = fread(&skip, 1, 4, mp4->mediafp);
 							len += fread(&num, 1, 4, mp4->mediafp);
 							num = BYTESWAP32(num);
+
+							if(num == 0)
+							{
+								//size of null
+								CloseSource((size_t)mp4);
+								mp4 = NULL;
+								break;
+							}
+
 							if (num * 8 <= qtsize - 8 - len)
 							{
 								if (mp4->metastsc_count > 0 && num != mp4->metasize_count)
 								{
 									mp4->indexcount = mp4->metasize_count;
 									if (mp4->metaoffsets) free(mp4->metaoffsets);
-									mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
-									if (mp4->metaoffsets)
+									if (mp4->metasize_count)
 									{
-										uint64_t *metaoffsets64 = NULL;
-										metaoffsets64 = (uint64_t *)malloc(num * 8);
-										if (metaoffsets64)
+										mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
+										if (mp4->metaoffsets)
 										{
-											uint64_t fileoffset = 0;
-											int stsc_pos = 0;
-											int stco_pos = 0;
-											len += fread(metaoffsets64, 1, num * 8, mp4->mediafp);
-											do
+											uint64_t *metaoffsets64 = NULL;
+											metaoffsets64 = (uint64_t *)malloc(num * 8);
+											if (metaoffsets64)
 											{
-												num--;
-												metaoffsets64[num] = BYTESWAP64(metaoffsets64[num]);
-											} while (num > 0);
+												uint64_t fileoffset = 0;
+												int stsc_pos = 0;
+												int stco_pos = 0;
+												len += fread(metaoffsets64, 1, num * 8, mp4->mediafp);
+												do
+												{
+													num--;
+													metaoffsets64[num] = BYTESWAP64(metaoffsets64[num]);
+												} while (num > 0);
 
-											fileoffset = metaoffsets64[0];
-											mp4->metaoffsets[0] = fileoffset;
-											//printf("%3d:%08x, delta = %08x\n", 0, (int)fileoffset, 0);
+												fileoffset = metaoffsets64[0];
+												mp4->metaoffsets[0] = fileoffset;
+												//printf("%3d:%08x, delta = %08x\n", 0, (int)fileoffset, 0);
 
-											num = 1;
-											while (num < mp4->metasize_count)
-											{
-												if (num != mp4->metastsc[stsc_pos].chunk_num - 1 && 0 == (num - (mp4->metastsc[stsc_pos].chunk_num - 1)) % mp4->metastsc[stsc_pos].samples)
-												{
-													stco_pos++;
-													fileoffset = (uint64_t)metaoffsets64[stco_pos];
-												}
-												else
+												num = 1;
+												while (num < mp4->metasize_count)
 												{
-													fileoffset += (uint64_t)mp4->metasizes[num - 1];
+													if (num != mp4->metastsc[stsc_pos].chunk_num - 1 && 0 == (num - (mp4->metastsc[stsc_pos].chunk_num - 1)) % mp4->metastsc[stsc_pos].samples)
+													{
+														stco_pos++;
+														fileoffset = (uint64_t)metaoffsets64[stco_pos];
+													}
+													else
+													{
+														fileoffset += (uint64_t)mp4->metasizes[num - 1];
+													}
+
+													mp4->metaoffsets[num] = fileoffset;
+													//int delta = metaoffsets[num] - metaoffsets[num - 1];
+													//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
+
+													num++;
 												}
 
-												mp4->metaoffsets[num] = fileoffset;
-												//int delta = metaoffsets[num] - metaoffsets[num - 1];
-												//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
+												if (mp4->metastsc) free(mp4->metastsc);
+												mp4->metastsc = NULL;
+												mp4->metastsc_count = 0;
 
-												num++;
+												free(metaoffsets64);
 											}
-
-											if (mp4->metastsc) free(mp4->metastsc);
-											mp4->metastsc = NULL;
-											mp4->metastsc_count = 0;
-
-											free(metaoffsets64);
 										}
 									}
+									else
+									{
+										//size of null
+										CloseSource((size_t)mp4);
+										mp4 = NULL;
+										break;
+									}
 								}
 								else
 								{
 									mp4->indexcount = num;
 									if (mp4->metaoffsets) free(mp4->metaoffsets);
@@ -536,14 +616,15 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 											mp4->metaoffsets[num] = BYTESWAP64(mp4->metaoffsets[num]);
 										} while (num > 0);
 									}
 								}
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stco
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 					else if (qttag == MAKEID('s', 't', 't', 's')) // time to samples
 					{
@@ -576,14 +657,15 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 									totaldur += duration;
 									mp4->metadatalength += (double)((double)samplecount * (double)duration / (double)mp4->meta_clockdemon);
 								}
 								mp4->basemetadataduration = mp4->metadatalength * (double)mp4->meta_clockdemon / (double)samples;
 							}
-							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
+							mp4->filepos += len;
+							LongSeek(mp4, qtsize - 8 - len); // skip over stco
 						}
 						else
-							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+							LongSeek(mp4, qtsize - 8);
 
 						NESTSIZE(qtsize);
 					}
 					else
 					{
@@ -593,10 +675,19 @@ size_t OpenMP4Source(char *filename, uint32_t traktype, uint32_t traksubtype)  /
 			else
 			{
 				break;
 			}
 		} while (len > 0);
+
+		if (mp4)
+		{
+			if (mp4->metasizes == NULL || mp4->metaoffsets == NULL)
+			{
+				CloseSource((size_t)mp4);
+				mp4 = NULL;
+			}
+		}
 	}
 	else
 	{
 		//	printf("Could not open %s for input\n", filename);
 		//	exit(1);
@@ -629,24 +720,36 @@ void CloseSource(size_t handle)
 
 	free(mp4);
 }
 
 
-uint32_t GetPayloadTime(size_t handle, uint32_t index, float *in, float *out)
+uint32_t GetPayloadTime(size_t handle, uint32_t index, double *in, double *out)
 {
 	mp4object *mp4 = (mp4object *)handle;
-	if (mp4 == NULL) return 0;
+	if (mp4 == NULL) return GPMF_ERROR_MEMORY;
 
-	if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return 1;
+	if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return GPMF_ERROR_MEMORY;
 
-	*in = (float)((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
-	*out = (float)((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
-	return 0;
+	*in = ((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
+	*out = ((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
+	return GPMF_OK;
 }
 
 
-
+uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, uint32_t *in_numerator, uint32_t *out_numerator, uint32_t *denominator)
+{
+    mp4object *mp4 = (mp4object *)handle;
+    if (mp4 == NULL) return GPMF_ERROR_MEMORY;
+    
+    if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in_numerator == NULL || out_numerator == NULL) return GPMF_ERROR_MEMORY;
+
+	*in_numerator = (uint32_t)(index * mp4->basemetadataduration);
+	*out_numerator = (uint32_t)((index + 1) * mp4->basemetadataduration);
+	*denominator = (uint32_t)mp4->meta_clockdemon;
+    
+    return GPMF_OK;
+}
 
 size_t OpenMP4SourceUDTA(char *filename)
 {
 	mp4object *mp4 = (mp4object *)malloc(sizeof(mp4object));
 	if (mp4 == NULL) return 0;
@@ -659,11 +762,12 @@ size_t OpenMP4SourceUDTA(char *filename)
 	mp4->mediafp = fopen(filename, "rb");
 #endif
 
 	if (mp4->mediafp)
 	{
-		uint32_t qttag, qtsize32, len;
+		uint32_t qttag, qtsize32;
+		size_t len;
 		int32_t nest = 0;
 		uint64_t nestsize[MAX_NEST_LEVEL] = { 0 };
 		uint64_t lastsize = 0, qtsize;
 
 		do
@@ -672,11 +776,11 @@ size_t OpenMP4SourceUDTA(char *filename)
 			len += fread(&qttag, 1, 4, mp4->mediafp);
 			if (len == 8)
 			{
 				if (!GPMF_VALID_FOURCC(qttag))
 				{
-					LONGSEEK(mp4->mediafp, lastsize - 8 - 8, SEEK_CUR);
+					LongSeek(mp4, lastsize - 8 - 8);
 
 					NESTSIZE(lastsize - 8);
 					continue;
 				}
 
@@ -699,11 +803,11 @@ size_t OpenMP4SourceUDTA(char *filename)
 				lastsize = qtsize;
 
 				if (qttag == MAKEID('m', 'd', 'a', 't') ||
 					qttag == MAKEID('f', 't', 'y', 'p'))
 				{
-					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+					LongSeek(mp4, qtsize - 8);
 					NESTSIZE(qtsize);
 					continue;
 				}
 
 				if (qttag == MAKEID('G', 'P', 'M', 'F'))
@@ -723,11 +827,11 @@ size_t OpenMP4SourceUDTA(char *filename)
 					return (size_t)mp4;  // not an MP4, RAW GPMF which has not inherent timing, assigning a during of 1second.
 				}
 				if (qttag != MAKEID('m', 'o', 'o', 'v') && //skip over all but these atoms
 					qttag != MAKEID('u', 'd', 't', 'a'))
 				{
-					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
+					LongSeek(mp4, qtsize - 8);
 					NESTSIZE(qtsize);
 					continue;
 				}
 				else
 				{
@@ -738,233 +842,290 @@ size_t OpenMP4SourceUDTA(char *filename)
 	}
 	return (size_t)mp4;
 }
 
 
-double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags)
+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;
+	int32_t ret;
+
 	if (mp4->indexcount < 1)
 		return 0.0;
 
-	if (mp4->indexcount > 3) // samples after first and before last are statistically the best, avoiding camera start up or shutdown anomollies. 
-	{
-		teststart++;
-		testend--;
-	}
-
-	uint32_t *payload = GetPayload(handle, NULL, teststart); // second payload
-	uint32_t payloadsize = GetPayloadSize(handle, teststart);
-	int32_t ret = GPMF_Init(ms, payload, payloadsize);
+	payload = GetPayload(handle, NULL, teststart); 
+	payloadsize = GetPayloadSize(handle, teststart);
+	ret = GPMF_Init(ms, payload, payloadsize);
 
 	if (ret != GPMF_OK)
 		goto cleanup;
 
 	{
+		uint64_t minimumtimestamp = 0;
+		uint64_t starttimestamp = 0;
+		uint64_t endtimestamp = 0;
 		uint32_t startsamples = 0;
 		uint32_t endsamples = 0;
-		uint32_t missing_samples = 0;
+		double intercept = 0.0;
+
+
 
-		while (ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
+		while (teststart < mp4->indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
 		{
-			missing_samples = 1;
 			teststart++;
 			payload = GetPayload(handle, payload, teststart); // second last payload
 			payloadsize = GetPayloadSize(handle, teststart);
 			ret = GPMF_Init(ms, payload, payloadsize);
 		}
 
-		if (missing_samples)
-		{
-			teststart++;   //samples after sensor start are statistically the best
-			payload = GetPayload(handle, payload, teststart);
-			payloadsize = GetPayloadSize(handle, teststart);
-			ret = GPMF_Init(ms, payload, payloadsize);
-		}
-
-		if (ret == GPMF_OK)
+		if (ret == GPMF_OK && payload)
 		{
-			uint32_t samples = GPMF_Repeat(ms);
+			uint32_t samples = GPMF_PayloadSampleCount(ms);
 			GPMF_stream find_stream;
 			GPMF_CopyState(ms, &find_stream);
+			if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
+				startsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)) - samples;
 
-			if (!(flags & GPMF_SAMPLE_RATE_PRECISE) && GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
+			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) // is this earliest in the payload, examine the other streams in this early payload.
 			{
-				startsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)) - samples;
+				GPMF_stream any_stream;
+				GPMF_Init(&any_stream, payload, payloadsize);
+
+				minimumtimestamp = starttimestamp;
+				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 < minimumtimestamp)
+						minimumtimestamp = timestamp;
+				}
+			}
 
-				payload = GetPayload(handle, payload, testend); // second last payload
+			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);
-				if (ret != GPMF_OK)
-					goto cleanup;
+			} while (testend > 0 && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS));
 
-				if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
+			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++)
 				{
-					GPMF_CopyState(ms, &find_stream);
-					if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
+					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) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
+
+					if (approxrate == 0.0)
+						approxrate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
+
+
+					while (time_stamp_scale >= 1)
 					{
-						endsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream));
-						rate = (double)(endsamples - startsamples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
-						goto cleanup;
+						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)minimumtimestamp - (double)starttimestamp) / time_stamp_scale) * rate;
 				}
-
-				rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
 			}
-			else // for increased precision, for older GPMF streams sometimes missing the total sample count 
+
+			if (rate == 0.0) //Timestamps didn't help weren't available
 			{
-				uint32_t payloadpos = 0, payloadcount = 0;
-				double slope, top = 0.0, bot = 0.0, meanX = 0, meanY = 0;
-				uint32_t *repeatarray = malloc(mp4->indexcount * 4 + 4);
-				memset(repeatarray, 0, mp4->indexcount * 4 + 4);
+				if (!(flags & GPMF_SAMPLE_RATE_PRECISE))
+				{
+					if (endsamples > startsamples)
+						rate = (double)(endsamples - startsamples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
 
-				samples = 0;
+					if (rate == 0.0)
+						rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
 
-				for (payloadpos = teststart; payloadpos < testend; payloadcount++, payloadpos++)
+					double in, out;
+					if (GPMF_OK == GetPayloadTime(handle, teststart, &in, &out))
+						intercept = (double)-in * rate;
+				}
+				else // for increased precision, for older GPMF streams sometimes missing the total sample count 
 				{
-					payload = GetPayload(handle, payload, payloadpos); // second last payload
-					payloadsize = GetPayloadSize(handle, payloadpos);
-					ret = GPMF_Init(ms, payload, payloadsize);
+					uint32_t payloadpos = 0, payloadcount = 0;
+					double slope, top = 0.0, bot = 0.0, meanX = 0, meanY = 0;
+					uint32_t *repeatarray = malloc(mp4->indexcount * 4 + 4);
+					memset(repeatarray, 0, mp4->indexcount * 4 + 4);
 
-					if (ret != GPMF_OK)
-						goto cleanup;
+					samples = 0;
 
-					if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
+					for (payloadpos = teststart; payloadpos <= testend; payloadpos++)
 					{
-						GPMF_stream find_stream2;
-						GPMF_CopyState(ms, &find_stream2);
+						payload = GetPayload(handle, payload, payloadpos); // second last payload
+						payloadsize = GetPayloadSize(handle, payloadpos);
+						ret = GPMF_Init(ms, payload, payloadsize);
 
-						if (GPMF_OK == GPMF_FindNext(&find_stream2, fourcc, GPMF_CURRENT_LEVEL)) // Count the instances, not the repeats
+						if (ret != GPMF_OK)
+							goto cleanup;
+
+						if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
 						{
-							if (repeatarray)
-							{
-								float in, out;
+							GPMF_stream find_stream2;
+							GPMF_CopyState(ms, &find_stream2);
+
+							payloadcount++;
 
-								do
+							if (GPMF_OK == GPMF_FindNext(&find_stream2, fourcc, GPMF_CURRENT_LEVEL)) // Count the instances, not the repeats
+							{
+								if (repeatarray)
 								{
-									samples++;
-								} while (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_CURRENT_LEVEL));
+									double in, out;
 
-								repeatarray[payloadpos] = samples;
-								meanY += (double)samples;
+									do
+									{
+										samples++;
+									} while (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_CURRENT_LEVEL));
 
-								GetPayloadTime(handle, payloadpos, &in, &out);
-								meanX += out;
-							}
-						}
-						else
-						{
-							uint32_t repeat = GPMF_Repeat(ms);
-							samples += repeat;
+									repeatarray[payloadpos] = samples;
+									meanY += (double)samples;
 
-							if (repeatarray)
+									if (GPMF_OK == GetPayloadTime(handle, payloadpos, &in, &out))
+										meanX += out;
+								}
+							}
+							else
 							{
-								float in, out;
+								uint32_t repeat = GPMF_PayloadSampleCount(ms);
+								samples += repeat;
+
+								if (repeatarray)
+								{
+									double in, out;
 
-								repeatarray[payloadpos] = samples;
-								meanY += (double)samples;
+									repeatarray[payloadpos] = samples;
+									meanY += (double)samples;
 
-								GetPayloadTime(handle, payloadpos, &in, &out);
-								meanX += out;
+									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++)
+					// 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)
 					{
-						float in, out;
-						GetPayloadTime(handle, payloadpos, &in, &out);
+						meanY /= (double)payloadcount;
+						meanX /= (double)payloadcount;
 
-						top += ((double)out - meanX)*((double)repeatarray[payloadpos] - meanY);
-						bot += ((double)out - meanX)*((double)out - meanX);
-					}
+						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;
+						slope = top / bot;
+						rate = slope;
 
+						// This sample code might be useful for compare data latency between channels.
+						intercept = meanY - slope * meanX;
 #if 0
-					// This sample code might be useful for compare data latency between channels.
+						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
 					{
-						double intercept;
-						intercept = meanY - slope*meanX;
-						printf("%c%c%c%c start offset = %f (%.3fms)\n", PRINTF_4CC(fourcc), intercept, 1000.0 * intercept / slope);
+						rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
 					}
-#endif
-					rate = slope;
-				}
-				else
-				{
-					rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
-				}
-
-				free(repeatarray);
 
-				goto cleanup;
+					free(repeatarray);
+				}
 			}
-		}
-	}
 
-cleanup:
-	if (payload) 
-	{
-		FreePayload(payload);
-		payload = NULL;
-	}
-
-	return rate;
-}
+			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 && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS));
 
+				if (endpayload > 0 && ret == GPMF_OK)
+				{
+					uint32_t totalsamples = endsamples - startsamples;
+					float timo = 0.0;
 
-double GetGPMFSampleRateAndTimes(size_t handle, GPMF_stream *gs, double rate, uint32_t index, double *in, double *out)
-{
-	mp4object *mp4 = (mp4object *)handle;
-	if (mp4 == NULL) return 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);
 
-	uint32_t key, insamples;
-	uint32_t repeat, outsamples;
-	GPMF_stream find_stream;
+					double first, last;
+					first = -intercept / rate - timo;
+					last = first + (double)totalsamples / rate;
 
-	if (gs == NULL || mp4->metaoffsets == 0 || mp4->indexcount == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return 0.0;
+					//printf("%c%c%c%c first sample at time %.3fms, last at %.3fms\n", PRINTF_4CC(fourcc), 1000.0*first, 1000.0*last);
 
-	key = GPMF_Key(gs);
-	repeat = GPMF_Repeat(gs);
-	if (rate == 0.0)
-		rate = GetGPMFSampleRate(handle, key, GPMF_SAMPLE_RATE_FAST);
+					if (firstsampletime) *firstsampletime = first;
 
-	if (rate == 0.0)
-	{
-		*in = *out = 0.0;
-		return 0.0;
+					if (lastsampletime) *lastsampletime = last;
+				}
+			}
+		}
 	}
 
-	GPMF_CopyState(gs, &find_stream);
-	if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
-	{
-		outsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream));
-		insamples = outsamples - repeat;
+cleanup:
+	if (payload)
+		FreePayload(payload);
+	payload = NULL;
 
-		*in = ((double)insamples / (double)rate);
-		*out = ((double)outsamples / (double)rate);
-	}
-	else
-	{
-		// might too costly in some applications read all the samples to determine the clock jitter, here I return the estimate from the MP4 track.
-		*in = ((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
-		*out = ((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
-	}
 	return rate;
 }
+
diff --git a/demo/GPMF_mp4reader.h b/demo/GPMF_mp4reader.h
index 423f865..3556b66 100644
--- a/demo/GPMF_mp4reader.h
+++ b/demo/GPMF_mp4reader.h
@@ -62,10 +62,12 @@ typedef struct mp4object
 	uint32_t clockdemon, clockcount;
 	uint32_t trak_clockdemon, trak_clockcount;
 	uint32_t meta_clockdemon, meta_clockcount;
 	double basemetadataduration;
 	FILE *mediafp;
+	uint64_t filesize;
+	uint64_t filepos;
 } mp4object;
 
 #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))
 
@@ -99,20 +101,19 @@ 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 GetNumberPayloads(size_t handle);
 uint32_t *GetPayload(size_t handle, uint32_t *lastpayload, uint32_t index);
-void SavePayload(size_t handle, uint32_t *payload, 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, float *in, float *out); //MP4 timestamps for the payload
+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, uint32_t *in_numerator, uint32_t *out_numerator, uint32_t *denominator);
 
 #define GPMF_SAMPLE_RATE_FAST		0
 #define GPMF_SAMPLE_RATE_PRECISE	1
 
-double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags);
-double GetGPMFSampleRateAndTimes(size_t handle, GPMF_stream *gs, double lastrate, uint32_t index, double *in, double *out); //Jitter corrected sample(s) time, if lastrate is unknown, send 0.0 and it will be computed
+double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags, double *in, double *out);
 
 #ifdef __cplusplus
 }
 #endif
 

Vulnerable code:  *  @version 1.2.1
		int32_t nestsize = (int32_t)ms->nest_size[ms->nest_level];
 *  @version 1.1.0
	GPMF_KEY_TOTAL_SAMPLES =	MAKEID('T','S','M','P'),//TOTL - Total Sample Count including the current payload 	
			float in = 0.0, out = 0.0; //times
				double rate = GetGPMFSampleRate(mp4, fourcc, GPMF_SAMPLE_RATE_PRECISE);// GPMF_SAMPLE_RATE_FAST);
				printf("%c%c%c%c sampling rate = %f Hz\n", PRINTF_4CC(fourcc), rate);
*  @version 1.2.1
*  (C) Copyright 2017 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
/* This is not an elegant MP4 parser, only used to help demonstrate extraction of MP4 */

#define LONGSEEK  _fseeki64
#define LONGSEEK  fseeko

			LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
			fread(MP4buffer, 1, mp4->metasizes[index], mp4->mediafp);
			return MP4buffer;
void SavePayload(size_t handle, uint32_t *payload, uint32_t index)
	mp4object *mp4 = (mp4object *)handle;
	if (mp4 == NULL) return;

	uint32_t *MP4buffer = NULL;
	if (index < mp4->indexcount && mp4->mediafp && payload)
		LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
		fwrite(payload, 1, mp4->metasizes[index], mp4->mediafp);
	return;


			if (len == 8)
					LONGSEEK(mp4->mediafp, lastsize - 8 - 8, SEEK_CUR);

					NESTSIZE(lastsize - 8);
					continue;
					fread(&qtsize, 1, 8, mp4->mediafp);
					qttag == MAKEID('u', 'd', 't', 'a'))
					LONGSEEK(mediafp, qtsize - 8, SEEK_CUR);
					qttag != MAKEID('a', 'l', 'i', 's') &&
					qttag != MAKEID('a', 'l', 'i', 's') &&
					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over mvhd
						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over mvhd
						if (temp != MAKEID('a', 'l', 'i', 's'))
						LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over hldr
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsd
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
								mp4->metastsc = (SampleToChunk *)malloc(num * 12);
								if (mp4->metastsc)
									uint32_t total_stsc = num;
									len += fread(mp4->metastsc, 1, num * sizeof(SampleToChunk), mp4->mediafp);

									do
										num--;
										mp4->metastsc[num].chunk_num = BYTESWAP32(mp4->metastsc[num].chunk_num);
										mp4->metastsc[num].samples = BYTESWAP32(mp4->metastsc[num].samples);
										mp4->metastsc[num].id = BYTESWAP32(mp4->metastsc[num].id);
									} while (num > 0);
								}
								if (mp4->metastsc_count == 1 && mp4->metastsc[0].samples == 1) // Simplify if the stsc is not reporting any grouped chunks.
									if (mp4->metastsc) free(mp4->metastsc);
									mp4->metastsc = NULL;
									mp4->metastsc_count = 0;
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsx
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
								mp4->metasizes = (uint32_t *)malloc(num * 4);
								if (mp4->metasizes)
									if (equalsamplesize == 0)
										len += fread(mp4->metasizes, 1, num * 4, mp4->mediafp);
										do
											num--;
											mp4->metasizes[num] = BYTESWAP32(mp4->metasizes[num]);
										} while (num > 0);
									}
									else
									{
										equalsamplesize = BYTESWAP32(equalsamplesize);
										do
											num--;
											mp4->metasizes[num] = equalsamplesize;
										} while (num > 0);
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stsz
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
									mp4->indexcount = mp4->metasize_count;
									mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
									if (mp4->metaoffsets)
										uint32_t *metaoffsets32 = NULL;
										metaoffsets32 = (uint32_t *)malloc(num * 4);
										if (metaoffsets32)
											uint64_t fileoffset = 0;
											int stsc_pos = 0;
											int stco_pos = 0;
											int repeat = 1;
											len += fread(metaoffsets32, 1, num * 4, mp4->mediafp);
											do
											{
												num--;
												metaoffsets32[num] = BYTESWAP32(metaoffsets32[num]);
											} while (num > 0);

											mp4->metaoffsets[0] = fileoffset = metaoffsets32[stco_pos];
											num = 1;
											while (num < mp4->metasize_count)
												if (stsc_pos + 1 < (int)mp4->metastsc_count && num == stsc_pos)
													stco_pos++; stsc_pos++;
													fileoffset = (uint64_t)metaoffsets32[stco_pos];
													repeat = 1;
												}
												else if (repeat == mp4->metastsc[stsc_pos].samples)
												{
													stco_pos++;
													fileoffset = (uint64_t)metaoffsets32[stco_pos];
													repeat = 1;
												}
												else
													fileoffset += (uint64_t)mp4->metasizes[num - 1];
													repeat++;
												mp4->metaoffsets[num] = fileoffset;
												//int delta = metaoffsets[num] - metaoffsets[num - 1];
												//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
												num++;

											if (mp4->metastsc) free(mp4->metastsc);
											mp4->metastsc = NULL;
											mp4->metastsc_count = 0;

											free(metaoffsets32);
									mp4->metaoffsets = (uint64_t *)malloc(num * 8);
									if (mp4->metaoffsets)
										uint32_t *metaoffsets32 = NULL;
										metaoffsets32 = (uint32_t *)malloc(num * 4);
										if (metaoffsets32)
											size_t readlen = fread(metaoffsets32, 1, num * 4, mp4->mediafp);
											len += readlen;
											do
												num--;
												mp4->metaoffsets[num] = BYTESWAP32(metaoffsets32[num]);
											} while (num > 0);
											free(metaoffsets32);
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
									mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
									if (mp4->metaoffsets)
										uint64_t *metaoffsets64 = NULL;
										metaoffsets64 = (uint64_t *)malloc(num * 8);
										if (metaoffsets64)
											uint64_t fileoffset = 0;
											int stsc_pos = 0;
											int stco_pos = 0;
											len += fread(metaoffsets64, 1, num * 8, mp4->mediafp);
											do
												num--;
												metaoffsets64[num] = BYTESWAP64(metaoffsets64[num]);
											} while (num > 0);
											fileoffset = metaoffsets64[0];
											mp4->metaoffsets[0] = fileoffset;
											//printf("%3d:%08x, delta = %08x\n", 0, (int)fileoffset, 0);
											num = 1;
											while (num < mp4->metasize_count)
											{
												if (num != mp4->metastsc[stsc_pos].chunk_num - 1 && 0 == (num - (mp4->metastsc[stsc_pos].chunk_num - 1)) % mp4->metastsc[stsc_pos].samples)
												{
													stco_pos++;
													fileoffset = (uint64_t)metaoffsets64[stco_pos];
												}
												else
													fileoffset += (uint64_t)mp4->metasizes[num - 1];
												mp4->metaoffsets[num] = fileoffset;
												//int delta = metaoffsets[num] - metaoffsets[num - 1];
												//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);
												num++;

											if (mp4->metastsc) free(mp4->metastsc);
											mp4->metastsc = NULL;
											mp4->metastsc_count = 0;

											free(metaoffsets64);
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
							LONGSEEK(mp4->mediafp, qtsize - 8 - len, SEEK_CUR); // skip over stco
							LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
uint32_t GetPayloadTime(size_t handle, uint32_t index, float *in, float *out)
	if (mp4 == NULL) return 0;
	if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return 1;
	*in = (float)((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
	*out = (float)((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
	return 0;

		uint32_t qttag, qtsize32, len;
					LONGSEEK(mp4->mediafp, lastsize - 8 - 8, SEEK_CUR);
					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
					LONGSEEK(mp4->mediafp, qtsize - 8, SEEK_CUR);
double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags)
	if (mp4->indexcount > 3) // samples after first and before last are statistically the best, avoiding camera start up or shutdown anomollies. 
	{
		teststart++;
		testend--;
	}

	uint32_t *payload = GetPayload(handle, NULL, teststart); // second payload
	uint32_t payloadsize = GetPayloadSize(handle, teststart);
	int32_t ret = GPMF_Init(ms, payload, payloadsize);
		uint32_t missing_samples = 0;
		while (ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
			missing_samples = 1;
		if (missing_samples)
		{
			teststart++;   //samples after sensor start are statistically the best
			payload = GetPayload(handle, payload, teststart);
			payloadsize = GetPayloadSize(handle, teststart);
			ret = GPMF_Init(ms, payload, payloadsize);
		}

		if (ret == GPMF_OK)
			uint32_t samples = GPMF_Repeat(ms);
			if (!(flags & GPMF_SAMPLE_RATE_PRECISE) && GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
				startsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream)) - samples;
				payload = GetPayload(handle, payload, testend); // second last payload
				if (ret != GPMF_OK)
					goto cleanup;
				if (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
					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));
						rate = (double)(endsamples - startsamples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
						goto cleanup;

				rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
			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 = malloc(mp4->indexcount * 4 + 4);
				memset(repeatarray, 0, mp4->indexcount * 4 + 4);
				samples = 0;
				for (payloadpos = teststart; payloadpos < testend; payloadcount++, 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);
						if (GPMF_OK == GPMF_FindNext(&find_stream2, fourcc, GPMF_CURRENT_LEVEL)) // Count the instances, not the repeats
							if (repeatarray)
							{
								float in, out;
								do
									samples++;
								} while (GPMF_OK == GPMF_FindNext(ms, fourcc, GPMF_CURRENT_LEVEL));
								repeatarray[payloadpos] = samples;
								meanY += (double)samples;
								GetPayloadTime(handle, payloadpos, &in, &out);
								meanX += out;
							}
						}
						else
						{
							uint32_t repeat = GPMF_Repeat(ms);
							samples += repeat;
							if (repeatarray)
								float in, out;
								repeatarray[payloadpos] = samples;
								meanY += (double)samples;
								GetPayloadTime(handle, payloadpos, &in, &out);
								meanX += out;
				}

				// 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++)
						float in, out;
						GetPayloadTime(handle, payloadpos, &in, &out);
						top += ((double)out - meanX)*((double)repeatarray[payloadpos] - meanY);
						bot += ((double)out - meanX)*((double)out - meanX);
					}
					slope = top / bot;
					// This sample code might be useful for compare data latency between channels.
						double intercept;
						intercept = meanY - slope*meanX;
						printf("%c%c%c%c start offset = %f (%.3fms)\n", PRINTF_4CC(fourcc), intercept, 1000.0 * intercept / slope);
#endif
					rate = slope;
				}
				else
				{
					rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
				}

				free(repeatarray);
				goto cleanup;
		}
	}
cleanup:
	if (payload) 
	{
		FreePayload(payload);
		payload = NULL;
	}

	return rate;
}
double GetGPMFSampleRateAndTimes(size_t handle, GPMF_stream *gs, double rate, uint32_t index, double *in, double *out)
{
	mp4object *mp4 = (mp4object *)handle;
	if (mp4 == NULL) return 0.0;
	uint32_t key, insamples;
	uint32_t repeat, outsamples;
	GPMF_stream find_stream;
	if (gs == NULL || mp4->metaoffsets == 0 || mp4->indexcount == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return 0.0;
	key = GPMF_Key(gs);
	repeat = GPMF_Repeat(gs);
	if (rate == 0.0)
		rate = GetGPMFSampleRate(handle, key, GPMF_SAMPLE_RATE_FAST);
	if (rate == 0.0)
	{
		*in = *out = 0.0;
		return 0.0;
	GPMF_CopyState(gs, &find_stream);
	if (GPMF_OK == GPMF_FindPrev(&find_stream, GPMF_KEY_TOTAL_SAMPLES, GPMF_CURRENT_LEVEL))
	{
		outsamples = BYTESWAP32(*(uint32_t *)GPMF_RawData(&find_stream));
		insamples = outsamples - repeat;
		*in = ((double)insamples / (double)rate);
		*out = ((double)outsamples / (double)rate);
	}
	else
	{
		// might too costly in some applications read all the samples to determine the clock jitter, here I return the estimate from the MP4 track.
		*in = ((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
		*out = ((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
	}
void SavePayload(size_t handle, uint32_t *payload, uint32_t index);
uint32_t GetPayloadTime(size_t handle, uint32_t index, float *in, float *out); //MP4 timestamps for the payload
double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags);
double GetGPMFSampleRateAndTimes(size_t handle, GPMF_stream *gs, double lastrate, uint32_t index, double *in, double *out); //Jitter corrected sample(s) time, if lastrate is unknown, send 0.0 and it will be computed
Fixed code:  *  @version 1.2.2
		uint32_t nestsize = (uint32_t)ms->nest_size[ms->nest_level];
 *  @version 1.1.1
	GPMF_KEY_TOTAL_SAMPLES =	MAKEID('T','S','M','P'),//TSMP - Total Sample Count including the current payload 	
	GPMF_KEY_TIME_OFFSET =		MAKEID('T','I','M','O'),//TIMO - Time offset of the metadata stream that follows (single 4 byte float)
	GPMF_KEY_TIMING_OFFSET =	MAKEID('T','I','M','O'),//TIMO - duplicated, as older code might use the other version of TIMO
	GPMF_KEY_TIME_STAMP =		MAKEID('S','T','M','P'),//STMP - Time stamp for the first sample. 
	GPMF_KEY_TIME_STAMPS =		MAKEID('S','T','P','S'),//STPS - Stream of all the timestamps delivered (Generally don't use this. This would be if your sensor has no peroidic times, yet precision is required, or for debugging.) 
	if (mp4 == 0)
	{
		printf("error: %s is an invalid MP4/MOV\n", argv[1]);
		return -1;
	}

			double in = 0.0, out = 0.0; //times
				double in = 0.0, out = 0.0;
				double rate = GetGPMFSampleRate(mp4, fourcc, GPMF_SAMPLE_RATE_PRECISE, &in, &out);// GPMF_SAMPLE_RATE_FAST);
				printf("%c%c%c%c sampling rate = %f Hz (from %f to %f)\n", PRINTF_4CC(fourcc), rate, in, out);
*  @version 1.3.1
*  (C) Copyright 2017-2019 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.
/* This is not an elegant MP4 parser, only used to help demonstrate extraction of GPMF */
#include <sys/types.h>
#include <sys/stat.h>

#define LONGSEEK	_fseeki64
#define stat64		_stat64
#define LONGSEEK	fseeko
			if (mp4->filesize > mp4->metaoffsets[index]+mp4->metasizes[index])
			{
				LONGSEEK(mp4->mediafp, mp4->metaoffsets[index], SEEK_SET);
				fread(MP4buffer, 1, mp4->metasizes[index], mp4->mediafp);
				mp4->filepos = mp4->metaoffsets[index] + mp4->metasizes[index];
				return MP4buffer;
			}
void LongSeek(mp4object *mp4, int64_t offset)
	if (mp4 && offset)
		if (mp4->filepos + offset < mp4->filesize)
		{
			LONGSEEK(mp4->mediafp, offset, SEEK_CUR);
			mp4->filepos += offset;
		}
		else
		{
			mp4->filepos = mp4->filesize;
		}

	struct stat64 mp4stat;
	stat64(filename, &mp4stat);
	mp4->filesize = mp4stat.st_size;

	if (mp4->filesize < 64) return 0;


			mp4->filepos += len;
			if (len == 8 && mp4->filepos < mp4->filesize)
					CloseSource((size_t)mp4);
					mp4 = NULL;
					break;
					len = fread(&qtsize, 1, 8, mp4->mediafp);
					mp4->filepos += len;
					qttag == MAKEID('u', 'd', 't', 'a') ||
					qttag == MAKEID('f', 'r', 'e', 'e'))
					LongSeek(mp4, qtsize - 8);
					LongSeek(mp4, qtsize - 8);

						mp4->filepos += len;
						LongSeek(mp4, qtsize - 8 - len); // skip over mvhd

						mp4->filepos += len;
						LongSeek(mp4, qtsize - 8 - len); // skip over mvhd
						if (temp != MAKEID('a', 'l', 'i', 's') && temp != MAKEID('u', 'r', 'l', ' '))
						mp4->filepos += len;
						LongSeek(mp4, qtsize - 8 - len); // skip over hldr
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stsd
							LongSeek(mp4, qtsize - 8);
								if (num > 0)
									mp4->metastsc = (SampleToChunk *)malloc(num * sizeof(SampleToChunk));
									if (mp4->metastsc)
										len += fread(mp4->metastsc, 1, num * sizeof(SampleToChunk), mp4->mediafp);
										do
										{
											num--;
											mp4->metastsc[num].chunk_num = BYTESWAP32(mp4->metastsc[num].chunk_num);
											mp4->metastsc[num].samples = BYTESWAP32(mp4->metastsc[num].samples);
											mp4->metastsc[num].id = BYTESWAP32(mp4->metastsc[num].id);
										} while (num > 0);
									}
								}
								else
									//size of null
									CloseSource((size_t)mp4);
									mp4 = NULL;
									break;
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stsx
							LongSeek(mp4, qtsize - 8);
								if(num > 0)
									mp4->metasizes = (uint32_t *)malloc(num * 4);
									if (mp4->metasizes)
										if (equalsamplesize == 0)
											len += fread(mp4->metasizes, 1, num * 4, mp4->mediafp);
											do
											{
												num--;
												mp4->metasizes[num] = BYTESWAP32(mp4->metasizes[num]);
											} while (num > 0);
										}
										else
											equalsamplesize = BYTESWAP32(equalsamplesize);
											do
											{
												num--;
												mp4->metasizes[num] = equalsamplesize;
											} while (num > 0);
										}
								else
								{
									//size of null
									CloseSource((size_t)mp4);
									mp4 = NULL;
									break;
								}
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stsz
							LongSeek(mp4, qtsize - 8);
								uint32_t metastco_count = num;

									mp4->indexcount = num;
									if(num > 0)
										mp4->metaoffsets = (uint64_t *)malloc(num * 8);
										if (mp4->metaoffsets)
											uint32_t *metaoffsets32 = NULL;
											metaoffsets32 = (uint32_t *)malloc(num * 4);
											if (metaoffsets32)
												uint64_t fileoffset = 0;
												int stsc_pos = 0;
												int stco_pos = 0;
												int repeat = 1;
												len += fread(metaoffsets32, 1, num * 4, mp4->mediafp);
												do
													num--;
													metaoffsets32[num] = BYTESWAP32(metaoffsets32[num]);
												} while (num > 0);

												mp4->metaoffsets[0] = fileoffset = metaoffsets32[stco_pos];
												num = 1;
												while (num < mp4->indexcount)
													if ((uint32_t)repeat == mp4->metastsc[stsc_pos].samples)
													{
														if ((uint32_t)stco_pos + 1 < metastco_count)
														{
															stco_pos++;
															fileoffset = (uint64_t)metaoffsets32[stco_pos];
														}
														else
														{
															fileoffset += (uint64_t)mp4->metasizes[num - 1];
														}
														if ((uint32_t)stsc_pos + 1 < mp4->metastsc_count)
															if (mp4->metastsc[stsc_pos + 1].chunk_num == (uint32_t)stco_pos + 1)
																stsc_pos++;

														repeat = 1;
													}
													else
													{
														fileoffset += (uint64_t)mp4->metasizes[num - 1];
														repeat++;
													}

													mp4->metaoffsets[num] = fileoffset;
													//int delta = metaoffsets[num] - metaoffsets[num - 1];
													//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);

													num++;
												if (mp4->metastsc) free(mp4->metastsc);
												mp4->metastsc = NULL;
												mp4->metastsc_count = 0;
												free(metaoffsets32);
									else
									{
										//size of null
										CloseSource((size_t)mp4);
										mp4 = NULL;
										break;
									}
									if (num > 0)
										mp4->metaoffsets = (uint64_t *)malloc(num * 8);
										if (mp4->metaoffsets)
											uint32_t *metaoffsets32 = NULL;
											metaoffsets32 = (uint32_t *)malloc(num * 4);
											if (metaoffsets32)
												size_t readlen = fread(metaoffsets32, 1, num * 4, mp4->mediafp);
												len += readlen;
												do
												{
													num--;
													mp4->metaoffsets[num] = BYTESWAP32(metaoffsets32[num]);
												} while (num > 0);
												free(metaoffsets32);
											}
									else
									{
										//size of null
										CloseSource((size_t)mp4);
										mp4 = NULL;
										break;
									}
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stco
							LongSeek(mp4, qtsize - 8);

							if(num == 0)
							{
								//size of null
								CloseSource((size_t)mp4);
								mp4 = NULL;
								break;
							}

									if (mp4->metasize_count)
										mp4->metaoffsets = (uint64_t *)malloc(mp4->metasize_count * 8);
										if (mp4->metaoffsets)
											uint64_t *metaoffsets64 = NULL;
											metaoffsets64 = (uint64_t *)malloc(num * 8);
											if (metaoffsets64)
												uint64_t fileoffset = 0;
												int stsc_pos = 0;
												int stco_pos = 0;
												len += fread(metaoffsets64, 1, num * 8, mp4->mediafp);
												do
												{
													num--;
													metaoffsets64[num] = BYTESWAP64(metaoffsets64[num]);
												} while (num > 0);
												fileoffset = metaoffsets64[0];
												mp4->metaoffsets[0] = fileoffset;
												//printf("%3d:%08x, delta = %08x\n", 0, (int)fileoffset, 0);
												num = 1;
												while (num < mp4->metasize_count)
													if (num != mp4->metastsc[stsc_pos].chunk_num - 1 && 0 == (num - (mp4->metastsc[stsc_pos].chunk_num - 1)) % mp4->metastsc[stsc_pos].samples)
													{
														stco_pos++;
														fileoffset = (uint64_t)metaoffsets64[stco_pos];
													}
													else
													{
														fileoffset += (uint64_t)mp4->metasizes[num - 1];
													}

													mp4->metaoffsets[num] = fileoffset;
													//int delta = metaoffsets[num] - metaoffsets[num - 1];
													//printf("%3d:%08x, delta = %08x\n", num, (int)fileoffset, delta);

													num++;
												if (mp4->metastsc) free(mp4->metastsc);
												mp4->metastsc = NULL;
												mp4->metastsc_count = 0;
												free(metaoffsets64);
									else
									{
										//size of null
										CloseSource((size_t)mp4);
										mp4 = NULL;
										break;
									}
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stco
							LongSeek(mp4, qtsize - 8);
							mp4->filepos += len;
							LongSeek(mp4, qtsize - 8 - len); // skip over stco
							LongSeek(mp4, qtsize - 8);

		if (mp4)
		{
			if (mp4->metasizes == NULL || mp4->metaoffsets == NULL)
			{
				CloseSource((size_t)mp4);
				mp4 = NULL;
			}
		}
uint32_t GetPayloadTime(size_t handle, uint32_t index, double *in, double *out)
	if (mp4 == NULL) return GPMF_ERROR_MEMORY;
	if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in == NULL || out == NULL) return GPMF_ERROR_MEMORY;
	*in = ((double)index * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
	*out = ((double)(index + 1) * (double)mp4->basemetadataduration / (double)mp4->meta_clockdemon);
	return GPMF_OK;
uint32_t GetPayloadRationalTime(size_t handle, uint32_t index, uint32_t *in_numerator, uint32_t *out_numerator, uint32_t *denominator)
{
    mp4object *mp4 = (mp4object *)handle;
    if (mp4 == NULL) return GPMF_ERROR_MEMORY;
    
    if (mp4->metaoffsets == 0 || mp4->basemetadataduration == 0 || mp4->meta_clockdemon == 0 || in_numerator == NULL || out_numerator == NULL) return GPMF_ERROR_MEMORY;

	*in_numerator = (uint32_t)(index * mp4->basemetadataduration);
	*out_numerator = (uint32_t)((index + 1) * mp4->basemetadataduration);
	*denominator = (uint32_t)mp4->meta_clockdemon;
    
    return GPMF_OK;
}
		uint32_t qttag, qtsize32;
		size_t len;
					LongSeek(mp4, lastsize - 8 - 8);
					LongSeek(mp4, qtsize - 8);
					LongSeek(mp4, qtsize - 8);
double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags, double *firstsampletime, double *lastsampletime)
	uint32_t *payload;
	uint32_t payloadsize;
	int32_t ret;

	payload = GetPayload(handle, NULL, teststart); 
	payloadsize = GetPayloadSize(handle, teststart);
	ret = GPMF_Init(ms, payload, payloadsize);
		uint64_t minimumtimestamp = 0;
		uint64_t starttimestamp = 0;
		uint64_t endtimestamp = 0;
		double intercept = 0.0;


		while (teststart < mp4->indexcount && ret == GPMF_OK && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS))
		if (ret == GPMF_OK && payload)
			uint32_t samples = GPMF_PayloadSampleCount(ms);
			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) // is this earliest in the payload, examine the other streams in this early payload.
				GPMF_stream any_stream;
				GPMF_Init(&any_stream, payload, payloadsize);

				minimumtimestamp = starttimestamp;
				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 < minimumtimestamp)
						minimumtimestamp = timestamp;
				}
			}
			testend = mp4->indexcount;
			do
			{
				testend--;// last payload with the fourcc needed
				payload = GetPayload(handle, payload, testend);
			} while (testend > 0 && GPMF_OK != GPMF_FindNext(ms, fourcc, GPMF_RECURSE_LEVELS));
			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) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);

					if (approxrate == 0.0)
						approxrate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);


					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)minimumtimestamp - (double)starttimestamp) / time_stamp_scale) * rate;

			if (rate == 0.0) //Timestamps didn't help weren't available
				if (!(flags & GPMF_SAMPLE_RATE_PRECISE))
				{
					if (endsamples > startsamples)
						rate = (double)(endsamples - startsamples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
					if (rate == 0.0)
						rate = (double)(samples) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
					double in, out;
					if (GPMF_OK == GetPayloadTime(handle, teststart, &in, &out))
						intercept = (double)-in * 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 = 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;
						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) / (mp4->metadatalength * ((double)(testend - teststart + 1)) / (double)mp4->indexcount);
					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 && 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;
					//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;

	uint64_t filesize;
	uint64_t filepos;
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, uint32_t *in_numerator, uint32_t *out_numerator, uint32_t *denominator);
double GetGPMFSampleRate(size_t handle, uint32_t fourcc, uint32_t flags, double *in, double *out);