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


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);