16
|
1 /*
|
|
2 * The contents of this file are subject to the Mozilla Public
|
|
3 * License Version 1.1 (the "License"); you may not use this file
|
|
4 * except in compliance with the License. You may obtain a copy of
|
|
5 * the License at http://www.mozilla.org/MPL/
|
|
6 *
|
|
7 * Software distributed under the License is distributed on an "AS
|
|
8 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
9 * implied. See the License for the specific language governing
|
|
10 * rights and limitations under the License.
|
|
11 *
|
|
12 * The Original Code is MPEG4IP.
|
|
13 *
|
|
14 * The Initial Developer of the Original Code is Cisco Systems Inc.
|
|
15 * Portions created by Cisco Systems Inc. are
|
|
16 * Copyright (C) Cisco Systems Inc. 2001 - 2004. All Rights Reserved.
|
|
17 *
|
|
18 * 3GPP features implementation is based on 3GPP's TS26.234-v5.60,
|
|
19 * and was contributed by Ximpo Group Ltd.
|
|
20 *
|
|
21 * Portions created by Ximpo Group Ltd. are
|
|
22 * Copyright (C) Ximpo Group Ltd. 2003, 2004. All Rights Reserved.
|
|
23 *
|
|
24 * Contributor(s):
|
|
25 * Dave Mackie dmackie@cisco.com
|
|
26 * Alix Marchandise-Franquet alix@cisco.com
|
|
27 * Ximpo Group Ltd. mp4v2@ximpo.com
|
|
28 */
|
|
29
|
|
30 #include "mp4common.h"
|
|
31
|
|
32 #define AMR_UNINITIALIZED -1
|
|
33 #define AMR_TRUE 0
|
|
34 #define AMR_FALSE 1
|
|
35
|
|
36 MP4Track::MP4Track(MP4File* pFile, MP4Atom* pTrakAtom)
|
|
37 {
|
|
38 m_pFile = pFile;
|
|
39 m_pTrakAtom = pTrakAtom;
|
|
40
|
|
41 m_lastStsdIndex = 0;
|
|
42 m_lastSampleFile = NULL;
|
|
43
|
|
44 m_cachedReadSampleId = MP4_INVALID_SAMPLE_ID;
|
|
45 m_pCachedReadSample = NULL;
|
|
46 m_cachedReadSampleSize = 0;
|
|
47
|
|
48 m_writeSampleId = 1;
|
|
49 m_fixedSampleDuration = 0;
|
|
50 m_pChunkBuffer = NULL;
|
|
51 m_chunkBufferSize = 0;
|
|
52 m_chunkSamples = 0;
|
|
53 m_chunkDuration = 0;
|
|
54
|
|
55 // m_bytesPerSample should be set to 1, except for the
|
|
56 // quicktime audio constant bit rate samples, which have non-1 values
|
|
57 m_bytesPerSample = 1;
|
|
58 m_samplesPerChunk = 0;
|
|
59 m_durationPerChunk = 0;
|
|
60 m_isAmr = AMR_UNINITIALIZED;
|
|
61 m_curMode = 0;
|
|
62
|
|
63 bool success = true;
|
|
64
|
|
65 MP4Integer32Property* pTrackIdProperty;
|
|
66 success &= m_pTrakAtom->FindProperty(
|
|
67 "trak.tkhd.trackId",
|
|
68 (MP4Property**)&pTrackIdProperty);
|
|
69 if (success) {
|
|
70 m_trackId = pTrackIdProperty->GetValue();
|
|
71 }
|
|
72
|
|
73 success &= m_pTrakAtom->FindProperty(
|
|
74 "trak.mdia.mdhd.timeScale",
|
|
75 (MP4Property**)&m_pTimeScaleProperty);
|
|
76 if (success) {
|
|
77 // default chunking is 1 second of samples
|
|
78 m_durationPerChunk = m_pTimeScaleProperty->GetValue();
|
|
79 }
|
|
80
|
|
81 success &= m_pTrakAtom->FindProperty(
|
|
82 "trak.tkhd.duration",
|
|
83 (MP4Property**)&m_pTrackDurationProperty);
|
|
84
|
|
85 success &= m_pTrakAtom->FindProperty(
|
|
86 "trak.mdia.mdhd.duration",
|
|
87 (MP4Property**)&m_pMediaDurationProperty);
|
|
88
|
|
89 success &= m_pTrakAtom->FindProperty(
|
|
90 "trak.tkhd.modificationTime",
|
|
91 (MP4Property**)&m_pTrackModificationProperty);
|
|
92
|
|
93 success &= m_pTrakAtom->FindProperty(
|
|
94 "trak.mdia.mdhd.modificationTime",
|
|
95 (MP4Property**)&m_pMediaModificationProperty);
|
|
96
|
|
97 success &= m_pTrakAtom->FindProperty(
|
|
98 "trak.mdia.hdlr.handlerType",
|
|
99 (MP4Property**)&m_pTypeProperty);
|
|
100
|
|
101 // get handles on sample size information
|
|
102
|
|
103 success &= m_pTrakAtom->FindProperty(
|
|
104 "trak.mdia.minf.stbl.stsz.sampleSize",
|
|
105 (MP4Property**)&m_pStszFixedSampleSizeProperty);
|
|
106
|
|
107 success &= m_pTrakAtom->FindProperty(
|
|
108 "trak.mdia.minf.stbl.stsz.sampleCount",
|
|
109 (MP4Property**)&m_pStszSampleCountProperty);
|
|
110
|
|
111 success &= m_pTrakAtom->FindProperty(
|
|
112 "trak.mdia.minf.stbl.stsz.entries.sampleSize",
|
|
113 (MP4Property**)&m_pStszSampleSizeProperty);
|
|
114
|
|
115 // get handles on information needed to map sample id's to file offsets
|
|
116
|
|
117 success &= m_pTrakAtom->FindProperty(
|
|
118 "trak.mdia.minf.stbl.stsc.entryCount",
|
|
119 (MP4Property**)&m_pStscCountProperty);
|
|
120
|
|
121 success &= m_pTrakAtom->FindProperty(
|
|
122 "trak.mdia.minf.stbl.stsc.entries.firstChunk",
|
|
123 (MP4Property**)&m_pStscFirstChunkProperty);
|
|
124
|
|
125 success &= m_pTrakAtom->FindProperty(
|
|
126 "trak.mdia.minf.stbl.stsc.entries.samplesPerChunk",
|
|
127 (MP4Property**)&m_pStscSamplesPerChunkProperty);
|
|
128
|
|
129 success &= m_pTrakAtom->FindProperty(
|
|
130 "trak.mdia.minf.stbl.stsc.entries.sampleDescriptionIndex",
|
|
131 (MP4Property**)&m_pStscSampleDescrIndexProperty);
|
|
132
|
|
133 success &= m_pTrakAtom->FindProperty(
|
|
134 "trak.mdia.minf.stbl.stsc.entries.firstSample",
|
|
135 (MP4Property**)&m_pStscFirstSampleProperty);
|
|
136
|
|
137 bool haveStco = m_pTrakAtom->FindProperty(
|
|
138 "trak.mdia.minf.stbl.stco.entryCount",
|
|
139 (MP4Property**)&m_pChunkCountProperty);
|
|
140
|
|
141 if (haveStco) {
|
|
142 success &= m_pTrakAtom->FindProperty(
|
|
143 "trak.mdia.minf.stbl.stco.entries.chunkOffset",
|
|
144 (MP4Property**)&m_pChunkOffsetProperty);
|
|
145 } else {
|
|
146 success &= m_pTrakAtom->FindProperty(
|
|
147 "trak.mdia.minf.stbl.co64.entryCount",
|
|
148 (MP4Property**)&m_pChunkCountProperty);
|
|
149
|
|
150 success &= m_pTrakAtom->FindProperty(
|
|
151 "trak.mdia.minf.stbl.co64.entries.chunkOffset",
|
|
152 (MP4Property**)&m_pChunkOffsetProperty);
|
|
153 }
|
|
154
|
|
155 // get handles on sample timing info
|
|
156
|
|
157 success &= m_pTrakAtom->FindProperty(
|
|
158 "trak.mdia.minf.stbl.stts.entryCount",
|
|
159 (MP4Property**)&m_pSttsCountProperty);
|
|
160
|
|
161 success &= m_pTrakAtom->FindProperty(
|
|
162 "trak.mdia.minf.stbl.stts.entries.sampleCount",
|
|
163 (MP4Property**)&m_pSttsSampleCountProperty);
|
|
164
|
|
165 success &= m_pTrakAtom->FindProperty(
|
|
166 "trak.mdia.minf.stbl.stts.entries.sampleDelta",
|
|
167 (MP4Property**)&m_pSttsSampleDeltaProperty);
|
|
168
|
|
169 // get handles on rendering offset info if it exists
|
|
170
|
|
171 m_pCttsCountProperty = NULL;
|
|
172 m_pCttsSampleCountProperty = NULL;
|
|
173 m_pCttsSampleOffsetProperty = NULL;
|
|
174
|
|
175 bool haveCtts = m_pTrakAtom->FindProperty(
|
|
176 "trak.mdia.minf.stbl.ctts.entryCount",
|
|
177 (MP4Property**)&m_pCttsCountProperty);
|
|
178
|
|
179 if (haveCtts) {
|
|
180 success &= m_pTrakAtom->FindProperty(
|
|
181 "trak.mdia.minf.stbl.ctts.entries.sampleCount",
|
|
182 (MP4Property**)&m_pCttsSampleCountProperty);
|
|
183
|
|
184 success &= m_pTrakAtom->FindProperty(
|
|
185 "trak.mdia.minf.stbl.ctts.entries.sampleOffset",
|
|
186 (MP4Property**)&m_pCttsSampleOffsetProperty);
|
|
187 }
|
|
188
|
|
189 // get handles on sync sample info if it exists
|
|
190
|
|
191 m_pStssCountProperty = NULL;
|
|
192 m_pStssSampleProperty = NULL;
|
|
193
|
|
194 bool haveStss = m_pTrakAtom->FindProperty(
|
|
195 "trak.mdia.minf.stbl.stss.entryCount",
|
|
196 (MP4Property**)&m_pStssCountProperty);
|
|
197
|
|
198 if (haveStss) {
|
|
199 success &= m_pTrakAtom->FindProperty(
|
|
200 "trak.mdia.minf.stbl.stss.entries.sampleNumber",
|
|
201 (MP4Property**)&m_pStssSampleProperty);
|
|
202 }
|
|
203
|
|
204 // edit list
|
|
205 InitEditListProperties();
|
|
206
|
|
207 // was everything found?
|
|
208 if (!success) {
|
|
209 throw new MP4Error("invalid track", "MP4Track::MP4Track");
|
|
210 }
|
|
211 CalculateBytesPerSample();
|
|
212 }
|
|
213
|
|
214 MP4Track::~MP4Track()
|
|
215 {
|
|
216 MP4Free(m_pCachedReadSample);
|
|
217 MP4Free(m_pChunkBuffer);
|
|
218 }
|
|
219
|
|
220 const char* MP4Track::GetType()
|
|
221 {
|
|
222 return m_pTypeProperty->GetValue();
|
|
223 }
|
|
224
|
|
225 void MP4Track::SetType(const char* type)
|
|
226 {
|
|
227 m_pTypeProperty->SetValue(MP4NormalizeTrackType(type,
|
|
228 m_pFile->GetVerbosity()));
|
|
229 }
|
|
230
|
|
231 void MP4Track::ReadSample(
|
|
232 MP4SampleId sampleId,
|
|
233 u_int8_t** ppBytes,
|
|
234 u_int32_t* pNumBytes,
|
|
235 MP4Timestamp* pStartTime,
|
|
236 MP4Duration* pDuration,
|
|
237 MP4Duration* pRenderingOffset,
|
|
238 bool* pIsSyncSample)
|
|
239 {
|
|
240 if (sampleId == MP4_INVALID_SAMPLE_ID) {
|
|
241 throw new MP4Error("sample id can't be zero",
|
|
242 "MP4Track::ReadSample");
|
|
243 }
|
|
244
|
|
245 // handle unusual case of wanting to read a sample
|
|
246 // that is still sitting in the write chunk buffer
|
|
247 if (m_pChunkBuffer && sampleId >= m_writeSampleId - m_chunkSamples) {
|
|
248 WriteChunkBuffer();
|
|
249 }
|
|
250
|
|
251 FILE* pFile = GetSampleFile(sampleId);
|
|
252
|
|
253 if (pFile == (FILE*)-1) {
|
|
254 throw new MP4Error("sample is located in an inaccessible file",
|
|
255 "MP4Track::ReadSample");
|
|
256 }
|
|
257
|
|
258 u_int64_t fileOffset = GetSampleFileOffset(sampleId);
|
|
259
|
|
260 u_int32_t sampleSize = GetSampleSize(sampleId);
|
|
261 if (*ppBytes != NULL && *pNumBytes < sampleSize) {
|
|
262 throw new MP4Error("sample buffer is too small",
|
|
263 "MP4Track::ReadSample");
|
|
264 }
|
|
265 *pNumBytes = sampleSize;
|
|
266
|
|
267 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
268 printf("ReadSample: track %u id %u offset 0x"X64" size %u (0x%x)\n",
|
|
269 m_trackId, sampleId, fileOffset, *pNumBytes, *pNumBytes));
|
|
270
|
|
271 bool bufferMalloc = false;
|
|
272 if (*ppBytes == NULL) {
|
|
273 *ppBytes = (u_int8_t*)MP4Malloc(*pNumBytes);
|
|
274 bufferMalloc = true;
|
|
275 }
|
|
276
|
|
277 u_int64_t oldPos = m_pFile->GetPosition(pFile); // only used in mode == 'w'
|
|
278 try {
|
|
279 m_pFile->SetPosition(fileOffset, pFile);
|
|
280 m_pFile->ReadBytes(*ppBytes, *pNumBytes, pFile);
|
|
281
|
|
282 if (pStartTime || pDuration) {
|
|
283 GetSampleTimes(sampleId, pStartTime, pDuration);
|
|
284
|
|
285 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
286 printf("ReadSample: start "U64" duration "D64"\n",
|
|
287 (pStartTime ? *pStartTime : 0),
|
|
288 (pDuration ? *pDuration : 0)));
|
|
289 }
|
|
290 if (pRenderingOffset) {
|
|
291 *pRenderingOffset = GetSampleRenderingOffset(sampleId);
|
|
292
|
|
293 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
294 printf("ReadSample: renderingOffset "D64"\n",
|
|
295 *pRenderingOffset));
|
|
296 }
|
|
297 if (pIsSyncSample) {
|
|
298 *pIsSyncSample = IsSyncSample(sampleId);
|
|
299
|
|
300 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
301 printf("ReadSample: isSyncSample %u\n",
|
|
302 *pIsSyncSample));
|
|
303 }
|
|
304 }
|
|
305
|
|
306 catch (MP4Error* e) {
|
|
307 if (bufferMalloc) {
|
|
308 // let's not leak memory
|
|
309 MP4Free(*ppBytes);
|
|
310 *ppBytes = NULL;
|
|
311 }
|
|
312 if (m_pFile->GetMode() == 'w') {
|
|
313 m_pFile->SetPosition(oldPos, pFile);
|
|
314 }
|
|
315 throw e;
|
|
316 }
|
|
317
|
|
318 if (m_pFile->GetMode() == 'w') {
|
|
319 m_pFile->SetPosition(oldPos, pFile);
|
|
320 }
|
|
321 }
|
|
322
|
|
323 void MP4Track::ReadSampleFragment(
|
|
324 MP4SampleId sampleId,
|
|
325 u_int32_t sampleOffset,
|
|
326 u_int16_t sampleLength,
|
|
327 u_int8_t* pDest)
|
|
328 {
|
|
329 if (sampleId == MP4_INVALID_SAMPLE_ID) {
|
|
330 throw new MP4Error("invalid sample id",
|
|
331 "MP4Track::ReadSampleFragment");
|
|
332 }
|
|
333
|
|
334 if (sampleId != m_cachedReadSampleId) {
|
|
335 MP4Free(m_pCachedReadSample);
|
|
336 m_pCachedReadSample = NULL;
|
|
337 m_cachedReadSampleSize = 0;
|
|
338 m_cachedReadSampleId = MP4_INVALID_SAMPLE_ID;
|
|
339
|
|
340 ReadSample(
|
|
341 sampleId,
|
|
342 &m_pCachedReadSample,
|
|
343 &m_cachedReadSampleSize);
|
|
344
|
|
345 m_cachedReadSampleId = sampleId;
|
|
346 }
|
|
347
|
|
348 if (sampleOffset + sampleLength > m_cachedReadSampleSize) {
|
|
349 throw new MP4Error("offset and/or length are too large",
|
|
350 "MP4Track::ReadSampleFragment");
|
|
351 }
|
|
352
|
|
353 memcpy(pDest, &m_pCachedReadSample[sampleOffset], sampleLength);
|
|
354 }
|
|
355
|
|
356 void MP4Track::WriteSample(
|
|
357 const u_int8_t* pBytes,
|
|
358 u_int32_t numBytes,
|
|
359 MP4Duration duration,
|
|
360 MP4Duration renderingOffset,
|
|
361 bool isSyncSample)
|
|
362 {
|
|
363 u_int8_t curMode = 0;
|
|
364
|
|
365 VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
|
|
366 printf("WriteSample: track %u id %u size %u (0x%x) ",
|
|
367 m_trackId, m_writeSampleId, numBytes, numBytes));
|
|
368
|
|
369 if (pBytes == NULL && numBytes > 0) {
|
|
370 throw new MP4Error("no sample data", "MP4WriteSample");
|
|
371 }
|
|
372
|
|
373 if (m_isAmr == AMR_UNINITIALIZED ) {
|
|
374 // figure out if this is an AMR audio track
|
|
375 if (m_pTrakAtom->FindAtom("trak.mdia.minf.stbl.stsd.samr") ||
|
|
376 m_pTrakAtom->FindAtom("trak.mdia.minf.stbl.stsd.sawb")) {
|
|
377 m_isAmr = AMR_TRUE;
|
|
378 m_curMode = (pBytes[0] >> 3) & 0x000F;
|
|
379 } else {
|
|
380 m_isAmr = AMR_FALSE;
|
|
381 }
|
|
382 }
|
|
383
|
|
384 if (m_isAmr == AMR_TRUE) {
|
|
385 curMode = (pBytes[0] >> 3) &0x000F; // The mode is in the first byte
|
|
386 }
|
|
387
|
|
388 if (duration == MP4_INVALID_DURATION) {
|
|
389 duration = GetFixedSampleDuration();
|
|
390 }
|
|
391
|
|
392 VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
|
|
393 printf("duration "U64"\n", duration));
|
|
394
|
|
395 if ((m_isAmr == AMR_TRUE) &&
|
|
396 (m_curMode != curMode)) {
|
|
397 WriteChunkBuffer();
|
|
398 m_curMode = curMode;
|
|
399 }
|
|
400
|
|
401 // append sample bytes to chunk buffer
|
|
402 m_pChunkBuffer = (u_int8_t*)MP4Realloc(m_pChunkBuffer,
|
|
403 m_chunkBufferSize + numBytes);
|
|
404 memcpy(&m_pChunkBuffer[m_chunkBufferSize], pBytes, numBytes);
|
|
405 m_chunkBufferSize += numBytes;
|
|
406 m_chunkSamples++;
|
|
407 m_chunkDuration += duration;
|
|
408
|
|
409 UpdateSampleSizes(m_writeSampleId, numBytes);
|
|
410
|
|
411 UpdateSampleTimes(duration);
|
|
412
|
|
413 UpdateRenderingOffsets(m_writeSampleId, renderingOffset);
|
|
414
|
|
415 UpdateSyncSamples(m_writeSampleId, isSyncSample);
|
|
416
|
|
417 if (IsChunkFull(m_writeSampleId)) {
|
|
418 WriteChunkBuffer();
|
|
419 m_curMode = curMode;
|
|
420 }
|
|
421
|
|
422 UpdateDurations(duration);
|
|
423
|
|
424 UpdateModificationTimes();
|
|
425
|
|
426 m_writeSampleId++;
|
|
427 }
|
|
428
|
|
429 void MP4Track::WriteChunkBuffer()
|
|
430 {
|
|
431 if (m_chunkBufferSize == 0) {
|
|
432 return;
|
|
433 }
|
|
434
|
|
435 u_int64_t chunkOffset = m_pFile->GetPosition();
|
|
436
|
|
437 // write chunk buffer
|
|
438 m_pFile->WriteBytes(m_pChunkBuffer, m_chunkBufferSize);
|
|
439
|
|
440 VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
|
|
441 printf("WriteChunk: track %u offset 0x"X64" size %u (0x%x) numSamples %u\n",
|
|
442 m_trackId, chunkOffset, m_chunkBufferSize,
|
|
443 m_chunkBufferSize, m_chunkSamples));
|
|
444
|
|
445 UpdateSampleToChunk(m_writeSampleId,
|
|
446 m_pChunkCountProperty->GetValue() + 1,
|
|
447 m_chunkSamples);
|
|
448
|
|
449 UpdateChunkOffsets(chunkOffset);
|
|
450
|
|
451 // clean up chunk buffer
|
|
452 MP4Free(m_pChunkBuffer);
|
|
453 m_pChunkBuffer = NULL;
|
|
454 m_chunkBufferSize = 0;
|
|
455 m_chunkSamples = 0;
|
|
456 m_chunkDuration = 0;
|
|
457 }
|
|
458
|
|
459 void MP4Track::FinishWrite()
|
|
460 {
|
|
461 // write out any remaining samples in chunk buffer
|
|
462 WriteChunkBuffer();
|
|
463
|
|
464 // record buffer size and bitrates
|
|
465 MP4BitfieldProperty* pBufferSizeProperty;
|
|
466
|
|
467 if (m_pTrakAtom->FindProperty(
|
|
468 "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.bufferSizeDB",
|
|
469 (MP4Property**)&pBufferSizeProperty)) {
|
|
470 pBufferSizeProperty->SetValue(GetMaxSampleSize());
|
|
471 }
|
|
472
|
|
473 MP4Integer32Property* pBitrateProperty;
|
|
474
|
|
475 if (m_pTrakAtom->FindProperty(
|
|
476 "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.maxBitrate",
|
|
477 (MP4Property**)&pBitrateProperty)) {
|
|
478 pBitrateProperty->SetValue(GetMaxBitrate());
|
|
479 }
|
|
480
|
|
481 if (m_pTrakAtom->FindProperty(
|
|
482 "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate",
|
|
483 (MP4Property**)&pBitrateProperty)) {
|
|
484 pBitrateProperty->SetValue(GetAvgBitrate());
|
|
485 }
|
|
486 }
|
|
487
|
|
488 bool MP4Track::IsChunkFull(MP4SampleId sampleId)
|
|
489 {
|
|
490 if (m_samplesPerChunk) {
|
|
491 return m_chunkSamples >= m_samplesPerChunk;
|
|
492 }
|
|
493
|
|
494 ASSERT(m_durationPerChunk);
|
|
495 return m_chunkDuration >= m_durationPerChunk;
|
|
496 }
|
|
497
|
|
498 u_int32_t MP4Track::GetNumberOfSamples()
|
|
499 {
|
|
500 return m_pStszSampleCountProperty->GetValue();
|
|
501 }
|
|
502
|
|
503 u_int32_t MP4Track::GetSampleSize(MP4SampleId sampleId)
|
|
504 {
|
|
505 u_int32_t fixedSampleSize =
|
|
506 m_pStszFixedSampleSizeProperty->GetValue();
|
|
507
|
|
508 if (fixedSampleSize != 0) {
|
|
509 return fixedSampleSize * m_bytesPerSample;
|
|
510 }
|
|
511 return m_bytesPerSample *
|
|
512 m_pStszSampleSizeProperty->GetValue(sampleId - 1);
|
|
513 }
|
|
514
|
|
515 u_int32_t MP4Track::GetMaxSampleSize()
|
|
516 {
|
|
517 u_int32_t fixedSampleSize =
|
|
518 m_pStszFixedSampleSizeProperty->GetValue();
|
|
519
|
|
520 if (fixedSampleSize != 0) {
|
|
521 return fixedSampleSize * m_bytesPerSample;
|
|
522 }
|
|
523
|
|
524 u_int32_t maxSampleSize = 0;
|
|
525 u_int32_t numSamples = m_pStszSampleSizeProperty->GetCount();
|
|
526 for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
|
|
527 u_int32_t sampleSize =
|
|
528 m_pStszSampleSizeProperty->GetValue(sid - 1);
|
|
529 if (sampleSize > maxSampleSize) {
|
|
530 maxSampleSize = sampleSize;
|
|
531 }
|
|
532 }
|
|
533 return maxSampleSize * m_bytesPerSample;
|
|
534 }
|
|
535
|
|
536 u_int64_t MP4Track::GetTotalOfSampleSizes()
|
|
537 {
|
|
538 uint64_t retval;
|
|
539 u_int32_t fixedSampleSize =
|
|
540 m_pStszFixedSampleSizeProperty->GetValue();
|
|
541
|
|
542 // if fixed sample size, just need to multiply by number of samples
|
|
543 if (fixedSampleSize != 0) {
|
|
544 retval = m_bytesPerSample;
|
|
545 retval *= fixedSampleSize;
|
|
546 retval *= GetNumberOfSamples();
|
|
547 return retval;
|
|
548 }
|
|
549
|
|
550 // else non-fixed sample size, sum them
|
|
551 u_int64_t totalSampleSizes = 0;
|
|
552 u_int32_t numSamples = m_pStszSampleSizeProperty->GetCount();
|
|
553 for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
|
|
554 u_int32_t sampleSize =
|
|
555 m_pStszSampleSizeProperty->GetValue(sid - 1);
|
|
556 totalSampleSizes += sampleSize;
|
|
557 }
|
|
558 return totalSampleSizes * m_bytesPerSample;
|
|
559 }
|
|
560
|
|
561 void MP4Track::UpdateSampleSizes(MP4SampleId sampleId, u_int32_t numBytes)
|
|
562 {
|
|
563 if (m_bytesPerSample > 1) {
|
|
564 if ((numBytes % m_bytesPerSample) != 0) {
|
|
565 // error
|
|
566 VERBOSE_ERROR(m_pFile->GetVerbosity(),
|
|
567 printf("UpdateSampleSize: numBytes %u not divisible by bytesPerSample %u sampleId %u\n",
|
|
568 numBytes, m_bytesPerSample, sampleId);
|
|
569 );
|
|
570 }
|
|
571 numBytes /= m_bytesPerSample;
|
|
572 }
|
|
573 // for first sample
|
|
574 if (sampleId == 1) {
|
|
575 if (numBytes > 0) {
|
|
576 // presume sample size is fixed
|
|
577 m_pStszFixedSampleSizeProperty->SetValue(numBytes);
|
|
578 } else {
|
|
579 // special case of first sample is zero bytes in length
|
|
580 // leave m_pStszFixedSampleSizeProperty at 0
|
|
581 // start recording variable sample sizes
|
|
582 m_pStszSampleSizeProperty->AddValue(0);
|
|
583 }
|
|
584
|
|
585 } else { // sampleId > 1
|
|
586 u_int32_t fixedSampleSize =
|
|
587 m_pStszFixedSampleSizeProperty->GetValue();
|
|
588
|
|
589 if (fixedSampleSize == 0 || numBytes != fixedSampleSize) {
|
|
590 // sample size is not fixed
|
|
591
|
|
592 if (fixedSampleSize) {
|
|
593 // need to clear fixed sample size
|
|
594 m_pStszFixedSampleSizeProperty->SetValue(0);
|
|
595
|
|
596 // and create sizes for all previous samples
|
|
597 for (MP4SampleId sid = 1; sid < sampleId; sid++) {
|
|
598 m_pStszSampleSizeProperty->AddValue(fixedSampleSize);
|
|
599 }
|
|
600 }
|
|
601
|
|
602 // add size value for this sample
|
|
603 m_pStszSampleSizeProperty->AddValue(numBytes);
|
|
604 }
|
|
605 }
|
|
606
|
|
607 m_pStszSampleCountProperty->IncrementValue();
|
|
608 }
|
|
609
|
|
610 u_int32_t MP4Track::GetAvgBitrate()
|
|
611 {
|
|
612 if (GetDuration() == 0) {
|
|
613 return 0;
|
|
614 }
|
|
615
|
|
616 double calc = (double) (GetTotalOfSampleSizes());
|
|
617 // this is a bit better - we use the whole duration
|
|
618 calc *= 8.0;
|
|
619 calc *= GetTimeScale();
|
|
620 calc /= (double) (GetDuration());
|
|
621 // we might want to think about rounding to the next 100 or 1000
|
|
622 return (uint32_t) ceil(calc);
|
|
623 }
|
|
624
|
|
625 u_int32_t MP4Track::GetMaxBitrate()
|
|
626 {
|
|
627 u_int32_t timeScale = GetTimeScale();
|
|
628 MP4SampleId numSamples = GetNumberOfSamples();
|
|
629 u_int32_t maxBytesPerSec = 0;
|
|
630 u_int32_t bytesThisSec = 0;
|
|
631 MP4Timestamp thisSecStart = 0;
|
|
632 MP4Timestamp lastSampleTime = 0;
|
|
633 uint32_t lastSampleSize = 0;
|
|
634
|
|
635 MP4SampleId thisSecStartSid = 1;
|
|
636 for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
|
|
637 uint32_t sampleSize;
|
|
638 MP4Timestamp sampleTime;
|
|
639
|
|
640 sampleSize = GetSampleSize(sid);
|
|
641 GetSampleTimes(sid, &sampleTime, NULL);
|
|
642
|
|
643 if (sampleTime < thisSecStart + timeScale) {
|
|
644 bytesThisSec += sampleSize;
|
|
645 lastSampleSize = sampleSize;
|
|
646 lastSampleTime = sampleTime;
|
|
647 } else {
|
|
648 // we've already written the last sample and sampleSize.
|
|
649 // this means that we've probably overflowed the last second
|
|
650 // calculate the time we've overflowed
|
|
651 MP4Duration overflow_dur =
|
|
652 (thisSecStart + timeScale) - lastSampleTime;
|
|
653 // calculate the duration of the last sample
|
|
654 MP4Duration lastSampleDur = sampleTime - lastSampleTime;
|
|
655 uint32_t overflow_bytes;
|
|
656 // now, calculate the number of bytes we overflowed. Round up.
|
|
657 overflow_bytes =
|
|
658 ((lastSampleSize * overflow_dur) + (lastSampleDur - 1)) / lastSampleDur;
|
|
659
|
|
660 if (bytesThisSec - overflow_bytes > maxBytesPerSec) {
|
|
661 maxBytesPerSec = bytesThisSec - overflow_bytes;
|
|
662 }
|
|
663
|
|
664 // now adjust the values for this sample. Remove the bytes
|
|
665 // from the first sample in this time frame
|
|
666 lastSampleTime = sampleTime;
|
|
667 lastSampleSize = sampleSize;
|
|
668 bytesThisSec += sampleSize;
|
|
669 bytesThisSec -= GetSampleSize(thisSecStartSid);
|
|
670 thisSecStartSid++;
|
|
671 GetSampleTimes(thisSecStartSid, &thisSecStart, NULL);
|
|
672 }
|
|
673 }
|
|
674
|
|
675 return maxBytesPerSec * 8;
|
|
676 }
|
|
677
|
|
678 u_int32_t MP4Track::GetSampleStscIndex(MP4SampleId sampleId)
|
|
679 {
|
|
680 u_int32_t stscIndex;
|
|
681 u_int32_t numStscs = m_pStscCountProperty->GetValue();
|
|
682
|
|
683 if (numStscs == 0) {
|
|
684 throw new MP4Error("No data chunks exist", "GetSampleStscIndex");
|
|
685 }
|
|
686
|
|
687 for (stscIndex = 0; stscIndex < numStscs; stscIndex++) {
|
|
688 if (sampleId < m_pStscFirstSampleProperty->GetValue(stscIndex)) {
|
|
689 ASSERT(stscIndex != 0);
|
|
690 stscIndex -= 1;
|
|
691 break;
|
|
692 }
|
|
693 }
|
|
694 if (stscIndex == numStscs) {
|
|
695 ASSERT(stscIndex != 0);
|
|
696 stscIndex -= 1;
|
|
697 }
|
|
698
|
|
699 return stscIndex;
|
|
700 }
|
|
701
|
|
702 FILE* MP4Track::GetSampleFile(MP4SampleId sampleId)
|
|
703 {
|
|
704 u_int32_t stscIndex =
|
|
705 GetSampleStscIndex(sampleId);
|
|
706
|
|
707 u_int32_t stsdIndex =
|
|
708 m_pStscSampleDescrIndexProperty->GetValue(stscIndex);
|
|
709
|
|
710 // check if the answer will be the same as last time
|
|
711 if (m_lastStsdIndex && stsdIndex == m_lastStsdIndex) {
|
|
712 return m_lastSampleFile;
|
|
713 }
|
|
714
|
|
715 MP4Atom* pStsdAtom =
|
|
716 m_pTrakAtom->FindAtom("trak.mdia.minf.stbl.stsd");
|
|
717 ASSERT(pStsdAtom);
|
|
718
|
|
719 MP4Atom* pStsdEntryAtom =
|
|
720 pStsdAtom->GetChildAtom(stsdIndex - 1);
|
|
721 ASSERT(pStsdEntryAtom);
|
|
722
|
|
723 MP4Integer16Property* pDrefIndexProperty = NULL;
|
|
724 pStsdEntryAtom->FindProperty(
|
|
725 "*.dataReferenceIndex",
|
|
726 (MP4Property**)&pDrefIndexProperty);
|
|
727
|
|
728 if (pDrefIndexProperty == NULL) {
|
|
729 throw new MP4Error("invalid stsd entry", "GetSampleFile");
|
|
730 }
|
|
731
|
|
732 u_int32_t drefIndex =
|
|
733 pDrefIndexProperty->GetValue();
|
|
734
|
|
735 MP4Atom* pDrefAtom =
|
|
736 m_pTrakAtom->FindAtom("trak.mdia.minf.dinf.dref");
|
|
737 ASSERT(pDrefAtom);
|
|
738
|
|
739 MP4Atom* pUrlAtom =
|
|
740 pDrefAtom->GetChildAtom(drefIndex - 1);
|
|
741 ASSERT(pUrlAtom);
|
|
742
|
|
743 FILE* pFile;
|
|
744
|
|
745 if (pUrlAtom->GetFlags() & 1) {
|
|
746 pFile = NULL; // self-contained
|
|
747 } else {
|
|
748 MP4StringProperty* pLocationProperty = NULL;
|
|
749 pUrlAtom->FindProperty(
|
|
750 "*.location",
|
|
751 (MP4Property**)&pLocationProperty);
|
|
752 ASSERT(pLocationProperty);
|
|
753
|
|
754 const char* url = pLocationProperty->GetValue();
|
|
755
|
|
756 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
757 printf("dref url = %s\n", url));
|
|
758
|
|
759 pFile = (FILE*)-1;
|
|
760
|
|
761 // attempt to open url if it's a file url
|
|
762 // currently this is the only thing we understand
|
|
763 if (!strncmp(url, "file:", 5)) {
|
|
764 const char* fileName = url + 5;
|
|
765 if (!strncmp(fileName, "//", 2)) {
|
|
766 fileName = strchr(fileName + 2, '/');
|
|
767 }
|
|
768 if (fileName) {
|
|
769 pFile = fopen(fileName, "rb");
|
|
770 if (!pFile) {
|
|
771 pFile = (FILE*)-1;
|
|
772 }
|
|
773 }
|
|
774 }
|
|
775 }
|
|
776
|
|
777 if (m_lastSampleFile) {
|
|
778 fclose(m_lastSampleFile);
|
|
779 }
|
|
780
|
|
781 // cache the answer
|
|
782 m_lastStsdIndex = stsdIndex;
|
|
783 m_lastSampleFile = pFile;
|
|
784
|
|
785 return pFile;
|
|
786 }
|
|
787
|
|
788 u_int64_t MP4Track::GetSampleFileOffset(MP4SampleId sampleId)
|
|
789 {
|
|
790 u_int32_t stscIndex =
|
|
791 GetSampleStscIndex(sampleId);
|
|
792
|
|
793 // firstChunk is the chunk index of the first chunk with
|
|
794 // samplesPerChunk samples in the chunk. There may be multiples -
|
|
795 // ie: several chunks with the same number of samples per chunk.
|
|
796 u_int32_t firstChunk =
|
|
797 m_pStscFirstChunkProperty->GetValue(stscIndex);
|
|
798
|
|
799 MP4SampleId firstSample =
|
|
800 m_pStscFirstSampleProperty->GetValue(stscIndex);
|
|
801
|
|
802 u_int32_t samplesPerChunk =
|
|
803 m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
|
|
804
|
|
805 // chunkId tells which is the absolute chunk number that this sample
|
|
806 // is stored in.
|
|
807 MP4ChunkId chunkId = firstChunk +
|
|
808 ((sampleId - firstSample) / samplesPerChunk);
|
|
809
|
|
810 // chunkOffset is the file offset (absolute) for the start of the chunk
|
|
811 u_int64_t chunkOffset = m_pChunkOffsetProperty->GetValue(chunkId - 1);
|
|
812
|
|
813 MP4SampleId firstSampleInChunk =
|
|
814 sampleId - ((sampleId - firstSample) % samplesPerChunk);
|
|
815
|
|
816 // need cumulative samples sizes from firstSample to sampleId - 1
|
|
817 u_int32_t sampleOffset = 0;
|
|
818 for (MP4SampleId i = firstSampleInChunk; i < sampleId; i++) {
|
|
819 sampleOffset += GetSampleSize(i);
|
|
820 }
|
|
821
|
|
822 return chunkOffset + sampleOffset;
|
|
823 }
|
|
824
|
|
825 void MP4Track::UpdateSampleToChunk(MP4SampleId sampleId,
|
|
826 MP4ChunkId chunkId, u_int32_t samplesPerChunk)
|
|
827 {
|
|
828 u_int32_t numStsc = m_pStscCountProperty->GetValue();
|
|
829
|
|
830 // if samplesPerChunk == samplesPerChunk of last entry
|
|
831 if (numStsc && samplesPerChunk ==
|
|
832 m_pStscSamplesPerChunkProperty->GetValue(numStsc-1)) {
|
|
833
|
|
834 // nothing to do
|
|
835
|
|
836 } else {
|
|
837 // add stsc entry
|
|
838 m_pStscFirstChunkProperty->AddValue(chunkId);
|
|
839 m_pStscSamplesPerChunkProperty->AddValue(samplesPerChunk);
|
|
840 m_pStscSampleDescrIndexProperty->AddValue(1);
|
|
841 m_pStscFirstSampleProperty->AddValue(sampleId - samplesPerChunk + 1);
|
|
842
|
|
843 m_pStscCountProperty->IncrementValue();
|
|
844 }
|
|
845 }
|
|
846
|
|
847 void MP4Track::UpdateChunkOffsets(u_int64_t chunkOffset)
|
|
848 {
|
|
849 if (m_pChunkOffsetProperty->GetType() == Integer32Property) {
|
|
850 ((MP4Integer32Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset);
|
|
851 } else {
|
|
852 ((MP4Integer64Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset);
|
|
853 }
|
|
854 m_pChunkCountProperty->IncrementValue();
|
|
855 }
|
|
856
|
|
857 MP4Duration MP4Track::GetFixedSampleDuration()
|
|
858 {
|
|
859 u_int32_t numStts = m_pSttsCountProperty->GetValue();
|
|
860
|
|
861 if (numStts == 0) {
|
|
862 return m_fixedSampleDuration;
|
|
863 }
|
|
864 if (numStts != 1) {
|
|
865 return MP4_INVALID_DURATION; // sample duration is not fixed
|
|
866 }
|
|
867 return m_pSttsSampleDeltaProperty->GetValue(0);
|
|
868 }
|
|
869
|
|
870 bool MP4Track::SetFixedSampleDuration(MP4Duration duration)
|
|
871 {
|
|
872 u_int32_t numStts = m_pSttsCountProperty->GetValue();
|
|
873
|
|
874 // setting this is only allowed before samples have been written
|
|
875 if (numStts != 0) {
|
|
876 return false;
|
|
877 }
|
|
878 m_fixedSampleDuration = duration;
|
|
879 return true;
|
|
880 }
|
|
881
|
|
882 void MP4Track::GetSampleTimes(MP4SampleId sampleId,
|
|
883 MP4Timestamp* pStartTime, MP4Duration* pDuration)
|
|
884 {
|
|
885 u_int32_t numStts = m_pSttsCountProperty->GetValue();
|
|
886 MP4SampleId sid = 1;
|
|
887 MP4Duration elapsed = 0;
|
|
888
|
|
889 for (u_int32_t sttsIndex = 0; sttsIndex < numStts; sttsIndex++) {
|
|
890 u_int32_t sampleCount =
|
|
891 m_pSttsSampleCountProperty->GetValue(sttsIndex);
|
|
892 u_int32_t sampleDelta =
|
|
893 m_pSttsSampleDeltaProperty->GetValue(sttsIndex);
|
|
894
|
|
895 if (sampleId <= sid + sampleCount - 1) {
|
|
896 if (pStartTime) {
|
|
897 *pStartTime = (sampleId - sid);
|
|
898 *pStartTime *= sampleDelta;
|
|
899 *pStartTime += elapsed;
|
|
900 }
|
|
901 if (pDuration) {
|
|
902 *pDuration = sampleDelta;
|
|
903 }
|
|
904 return;
|
|
905 }
|
|
906 sid += sampleCount;
|
|
907 elapsed += sampleCount * sampleDelta;
|
|
908 }
|
|
909
|
|
910 throw new MP4Error("sample id out of range",
|
|
911 "MP4Track::GetSampleTimes");
|
|
912 }
|
|
913
|
|
914 MP4SampleId MP4Track::GetSampleIdFromTime(
|
|
915 MP4Timestamp when,
|
|
916 bool wantSyncSample)
|
|
917 {
|
|
918 u_int32_t numStts = m_pSttsCountProperty->GetValue();
|
|
919 MP4SampleId sid = 1;
|
|
920 MP4Duration elapsed = 0;
|
|
921
|
|
922 for (u_int32_t sttsIndex = 0; sttsIndex < numStts; sttsIndex++) {
|
|
923 u_int32_t sampleCount =
|
|
924 m_pSttsSampleCountProperty->GetValue(sttsIndex);
|
|
925 u_int32_t sampleDelta =
|
|
926 m_pSttsSampleDeltaProperty->GetValue(sttsIndex);
|
|
927
|
|
928 if (sampleDelta == 0 && sttsIndex < numStts - 1) {
|
|
929 VERBOSE_READ(m_pFile->GetVerbosity(),
|
|
930 printf("Warning: Zero sample duration, stts entry %u\n",
|
|
931 sttsIndex));
|
|
932 }
|
|
933
|
|
934 MP4Duration d = when - elapsed;
|
|
935
|
|
936 if (d <= sampleCount * sampleDelta) {
|
|
937 MP4SampleId sampleId = sid;
|
|
938 if (sampleDelta) {
|
|
939 sampleId += (d / sampleDelta);
|
|
940 }
|
|
941
|
|
942 if (wantSyncSample) {
|
|
943 return GetNextSyncSample(sampleId);
|
|
944 }
|
|
945 return sampleId;
|
|
946 }
|
|
947
|
|
948 sid += sampleCount;
|
|
949 elapsed += sampleCount * sampleDelta;
|
|
950 }
|
|
951
|
|
952 throw new MP4Error("time out of range",
|
|
953 "MP4Track::GetSampleIdFromTime");
|
|
954
|
|
955 return 0; // satisfy MS compiler
|
|
956 }
|
|
957
|
|
958 void MP4Track::UpdateSampleTimes(MP4Duration duration)
|
|
959 {
|
|
960 u_int32_t numStts = m_pSttsCountProperty->GetValue();
|
|
961
|
|
962 // if duration == duration of last entry
|
|
963 if (numStts
|
|
964 && duration == m_pSttsSampleDeltaProperty->GetValue(numStts-1)) {
|
|
965 // increment last entry sampleCount
|
|
966 m_pSttsSampleCountProperty->IncrementValue(1, numStts-1);
|
|
967
|
|
968 } else {
|
|
969 // add stts entry, sampleCount = 1, sampleDuration = duration
|
|
970 m_pSttsSampleCountProperty->AddValue(1);
|
|
971 m_pSttsSampleDeltaProperty->AddValue(duration);
|
|
972 m_pSttsCountProperty->IncrementValue();;
|
|
973 }
|
|
974 }
|
|
975
|
|
976 u_int32_t MP4Track::GetSampleCttsIndex(MP4SampleId sampleId,
|
|
977 MP4SampleId* pFirstSampleId)
|
|
978 {
|
|
979 u_int32_t numCtts = m_pCttsCountProperty->GetValue();
|
|
980
|
|
981 MP4SampleId sid = 1;
|
|
982
|
|
983 for (u_int32_t cttsIndex = 0; cttsIndex < numCtts; cttsIndex++) {
|
|
984 u_int32_t sampleCount =
|
|
985 m_pCttsSampleCountProperty->GetValue(cttsIndex);
|
|
986
|
|
987 if (sampleId <= sid + sampleCount - 1) {
|
|
988 if (pFirstSampleId) {
|
|
989 *pFirstSampleId = sid;
|
|
990 }
|
|
991 return cttsIndex;
|
|
992 }
|
|
993 sid += sampleCount;
|
|
994 }
|
|
995
|
|
996 throw new MP4Error("sample id out of range",
|
|
997 "MP4Track::GetSampleCttsIndex");
|
|
998 return 0; // satisfy MS compiler
|
|
999 }
|
|
1000
|
|
1001 MP4Duration MP4Track::GetSampleRenderingOffset(MP4SampleId sampleId)
|
|
1002 {
|
|
1003 if (m_pCttsCountProperty == NULL) {
|
|
1004 return 0;
|
|
1005 }
|
|
1006 if (m_pCttsCountProperty->GetValue() == 0) {
|
|
1007 return 0;
|
|
1008 }
|
|
1009
|
|
1010 u_int32_t cttsIndex = GetSampleCttsIndex(sampleId);
|
|
1011
|
|
1012 return m_pCttsSampleOffsetProperty->GetValue(cttsIndex);
|
|
1013 }
|
|
1014
|
|
1015 void MP4Track::UpdateRenderingOffsets(MP4SampleId sampleId,
|
|
1016 MP4Duration renderingOffset)
|
|
1017 {
|
|
1018 // if ctts atom doesn't exist
|
|
1019 if (m_pCttsCountProperty == NULL) {
|
|
1020
|
|
1021 // no rendering offset, so nothing to do
|
|
1022 if (renderingOffset == 0) {
|
|
1023 return;
|
|
1024 }
|
|
1025
|
|
1026 // else create a ctts atom
|
|
1027 MP4Atom* pCttsAtom = AddAtom("trak.mdia.minf.stbl", "ctts");
|
|
1028
|
|
1029 // and get handles on the properties
|
|
1030 pCttsAtom->FindProperty(
|
|
1031 "ctts.entryCount",
|
|
1032 (MP4Property**)&m_pCttsCountProperty);
|
|
1033
|
|
1034 pCttsAtom->FindProperty(
|
|
1035 "ctts.entries.sampleCount",
|
|
1036 (MP4Property**)&m_pCttsSampleCountProperty);
|
|
1037
|
|
1038 pCttsAtom->FindProperty(
|
|
1039 "ctts.entries.sampleOffset",
|
|
1040 (MP4Property**)&m_pCttsSampleOffsetProperty);
|
|
1041
|
|
1042 // if this is not the first sample
|
|
1043 if (sampleId > 1) {
|
|
1044 // add a ctts entry for all previous samples
|
|
1045 // with rendering offset equal to zero
|
|
1046 m_pCttsSampleCountProperty->AddValue(sampleId - 1);
|
|
1047 m_pCttsSampleOffsetProperty->AddValue(0);
|
|
1048 m_pCttsCountProperty->IncrementValue();;
|
|
1049 }
|
|
1050 }
|
|
1051
|
|
1052 // ctts atom exists (now)
|
|
1053
|
|
1054 u_int32_t numCtts = m_pCttsCountProperty->GetValue();
|
|
1055
|
|
1056 // if renderingOffset == renderingOffset of last entry
|
|
1057 if (numCtts && renderingOffset
|
|
1058 == m_pCttsSampleOffsetProperty->GetValue(numCtts-1)) {
|
|
1059
|
|
1060 // increment last entry sampleCount
|
|
1061 m_pCttsSampleCountProperty->IncrementValue(1, numCtts-1);
|
|
1062
|
|
1063 } else {
|
|
1064 // add ctts entry, sampleCount = 1, sampleOffset = renderingOffset
|
|
1065 m_pCttsSampleCountProperty->AddValue(1);
|
|
1066 m_pCttsSampleOffsetProperty->AddValue(renderingOffset);
|
|
1067 m_pCttsCountProperty->IncrementValue();
|
|
1068 }
|
|
1069 }
|
|
1070
|
|
1071 void MP4Track::SetSampleRenderingOffset(MP4SampleId sampleId,
|
|
1072 MP4Duration renderingOffset)
|
|
1073 {
|
|
1074 // check if any ctts entries exist
|
|
1075 if (m_pCttsCountProperty == NULL
|
|
1076 || m_pCttsCountProperty->GetValue() == 0) {
|
|
1077 // if not then Update routine can be used
|
|
1078 // to create a ctts entry for samples before this one
|
|
1079 // and a ctts entry for this sample
|
|
1080 UpdateRenderingOffsets(sampleId, renderingOffset);
|
|
1081
|
|
1082 // but we also need a ctts entry
|
|
1083 // for all samples after this one
|
|
1084 u_int32_t afterSamples = GetNumberOfSamples() - sampleId;
|
|
1085
|
|
1086 if (afterSamples) {
|
|
1087 m_pCttsSampleCountProperty->AddValue(afterSamples);
|
|
1088 m_pCttsSampleOffsetProperty->AddValue(0);
|
|
1089 m_pCttsCountProperty->IncrementValue();;
|
|
1090 }
|
|
1091
|
|
1092 return;
|
|
1093 }
|
|
1094
|
|
1095 MP4SampleId firstSampleId;
|
|
1096 u_int32_t cttsIndex = GetSampleCttsIndex(sampleId, &firstSampleId);
|
|
1097
|
|
1098 // do nothing in the degenerate case
|
|
1099 if (renderingOffset ==
|
|
1100 m_pCttsSampleOffsetProperty->GetValue(cttsIndex)) {
|
|
1101 return;
|
|
1102 }
|
|
1103
|
|
1104 u_int32_t sampleCount =
|
|
1105 m_pCttsSampleCountProperty->GetValue(cttsIndex);
|
|
1106
|
|
1107 // if this sample has it's own ctts entry
|
|
1108 if (sampleCount == 1) {
|
|
1109 // then just set the value,
|
|
1110 // note we don't attempt to collapse entries
|
|
1111 m_pCttsSampleOffsetProperty->SetValue(renderingOffset, cttsIndex);
|
|
1112 return;
|
|
1113 }
|
|
1114
|
|
1115 MP4SampleId lastSampleId = firstSampleId + sampleCount - 1;
|
|
1116
|
|
1117 // else we share this entry with other samples
|
|
1118 // we need to insert our own entry
|
|
1119 if (sampleId == firstSampleId) {
|
|
1120 // our sample is the first one
|
|
1121 m_pCttsSampleCountProperty->
|
|
1122 InsertValue(1, cttsIndex);
|
|
1123 m_pCttsSampleOffsetProperty->
|
|
1124 InsertValue(renderingOffset, cttsIndex);
|
|
1125
|
|
1126 m_pCttsSampleCountProperty->
|
|
1127 SetValue(sampleCount - 1, cttsIndex + 1);
|
|
1128
|
|
1129 m_pCttsCountProperty->IncrementValue();
|
|
1130
|
|
1131 } else if (sampleId == lastSampleId) {
|
|
1132 // our sample is the last one
|
|
1133 m_pCttsSampleCountProperty->
|
|
1134 InsertValue(1, cttsIndex + 1);
|
|
1135 m_pCttsSampleOffsetProperty->
|
|
1136 InsertValue(renderingOffset, cttsIndex + 1);
|
|
1137
|
|
1138 m_pCttsSampleCountProperty->
|
|
1139 SetValue(sampleCount - 1, cttsIndex);
|
|
1140
|
|
1141 m_pCttsCountProperty->IncrementValue();
|
|
1142
|
|
1143 } else {
|
|
1144 // our sample is in the middle, UGH!
|
|
1145
|
|
1146 // insert our new entry
|
|
1147 m_pCttsSampleCountProperty->
|
|
1148 InsertValue(1, cttsIndex + 1);
|
|
1149 m_pCttsSampleOffsetProperty->
|
|
1150 InsertValue(renderingOffset, cttsIndex + 1);
|
|
1151
|
|
1152 // adjust count of previous entry
|
|
1153 m_pCttsSampleCountProperty->
|
|
1154 SetValue(sampleId - firstSampleId, cttsIndex);
|
|
1155
|
|
1156 // insert new entry for those samples beyond our sample
|
|
1157 m_pCttsSampleCountProperty->
|
|
1158 InsertValue(lastSampleId - sampleId, cttsIndex + 2);
|
|
1159 u_int32_t oldRenderingOffset =
|
|
1160 m_pCttsSampleOffsetProperty->GetValue(cttsIndex);
|
|
1161 m_pCttsSampleOffsetProperty->
|
|
1162 InsertValue(oldRenderingOffset, cttsIndex + 2);
|
|
1163
|
|
1164 m_pCttsCountProperty->IncrementValue(2);
|
|
1165 }
|
|
1166 }
|
|
1167
|
|
1168 bool MP4Track::IsSyncSample(MP4SampleId sampleId)
|
|
1169 {
|
|
1170 if (m_pStssCountProperty == NULL) {
|
|
1171 return true;
|
|
1172 }
|
|
1173
|
|
1174 u_int32_t numStss = m_pStssCountProperty->GetValue();
|
|
1175
|
|
1176 for (u_int32_t stssIndex = 0; stssIndex < numStss; stssIndex++) {
|
|
1177 MP4SampleId syncSampleId =
|
|
1178 m_pStssSampleProperty->GetValue(stssIndex);
|
|
1179
|
|
1180 if (sampleId == syncSampleId) {
|
|
1181 return true;
|
|
1182 }
|
|
1183 if (sampleId < syncSampleId) {
|
|
1184 break;
|
|
1185 }
|
|
1186 }
|
|
1187
|
|
1188 return false;
|
|
1189 }
|
|
1190
|
|
1191 // N.B. "next" is inclusive of this sample id
|
|
1192 MP4SampleId MP4Track::GetNextSyncSample(MP4SampleId sampleId)
|
|
1193 {
|
|
1194 if (m_pStssCountProperty == NULL) {
|
|
1195 return sampleId;
|
|
1196 }
|
|
1197
|
|
1198 u_int32_t numStss = m_pStssCountProperty->GetValue();
|
|
1199
|
|
1200 for (u_int32_t stssIndex = 0; stssIndex < numStss; stssIndex++) {
|
|
1201 MP4SampleId syncSampleId =
|
|
1202 m_pStssSampleProperty->GetValue(stssIndex);
|
|
1203
|
|
1204 if (sampleId > syncSampleId) {
|
|
1205 continue;
|
|
1206 }
|
|
1207 return syncSampleId;
|
|
1208 }
|
|
1209
|
|
1210 // LATER check stsh for alternate sample
|
|
1211
|
|
1212 return MP4_INVALID_SAMPLE_ID;
|
|
1213 }
|
|
1214
|
|
1215 void MP4Track::UpdateSyncSamples(MP4SampleId sampleId, bool isSyncSample)
|
|
1216 {
|
|
1217 if (isSyncSample) {
|
|
1218 // if stss atom exists, add entry
|
|
1219 if (m_pStssCountProperty) {
|
|
1220 m_pStssSampleProperty->AddValue(sampleId);
|
|
1221 m_pStssCountProperty->IncrementValue();
|
|
1222 } // else nothing to do (yet)
|
|
1223
|
|
1224 } else { // !isSyncSample
|
|
1225 // if stss atom doesn't exist, create one
|
|
1226 if (m_pStssCountProperty == NULL) {
|
|
1227
|
|
1228 MP4Atom* pStssAtom = AddAtom("trak.mdia.minf.stbl", "stss");
|
|
1229
|
|
1230 pStssAtom->FindProperty(
|
|
1231 "stss.entryCount",
|
|
1232 (MP4Property**)&m_pStssCountProperty);
|
|
1233
|
|
1234 pStssAtom->FindProperty(
|
|
1235 "stss.entries.sampleNumber",
|
|
1236 (MP4Property**)&m_pStssSampleProperty);
|
|
1237
|
|
1238 // set values for all samples that came before this one
|
|
1239 for (MP4SampleId sid = 1; sid < sampleId; sid++) {
|
|
1240 m_pStssSampleProperty->AddValue(sid);
|
|
1241 m_pStssCountProperty->IncrementValue();
|
|
1242 }
|
|
1243 } // else nothing to do
|
|
1244 }
|
|
1245 }
|
|
1246
|
|
1247 MP4Atom* MP4Track::AddAtom(char* parentName, char* childName)
|
|
1248 {
|
|
1249 MP4Atom* pChildAtom = MP4Atom::CreateAtom(childName);
|
|
1250
|
|
1251 MP4Atom* pParentAtom = m_pTrakAtom->FindAtom(parentName);
|
|
1252 ASSERT(pParentAtom);
|
|
1253
|
|
1254 pParentAtom->AddChildAtom(pChildAtom);
|
|
1255
|
|
1256 pChildAtom->Generate();
|
|
1257
|
|
1258 return pChildAtom;
|
|
1259 }
|
|
1260
|
|
1261 u_int64_t MP4Track::GetDuration()
|
|
1262 {
|
|
1263 return m_pMediaDurationProperty->GetValue();
|
|
1264 }
|
|
1265
|
|
1266 u_int32_t MP4Track::GetTimeScale()
|
|
1267 {
|
|
1268 return m_pTimeScaleProperty->GetValue();
|
|
1269 }
|
|
1270
|
|
1271 void MP4Track::UpdateDurations(MP4Duration duration)
|
|
1272 {
|
|
1273 // update media, track, and movie durations
|
|
1274 m_pMediaDurationProperty->SetValue(
|
|
1275 m_pMediaDurationProperty->GetValue() + duration);
|
|
1276
|
|
1277 MP4Duration movieDuration = ToMovieDuration(duration);
|
|
1278 m_pTrackDurationProperty->SetValue(
|
|
1279 m_pTrackDurationProperty->GetValue() + movieDuration);
|
|
1280
|
|
1281 m_pFile->UpdateDuration(m_pTrackDurationProperty->GetValue());
|
|
1282 }
|
|
1283
|
|
1284 MP4Duration MP4Track::ToMovieDuration(MP4Duration trackDuration)
|
|
1285 {
|
|
1286 return (trackDuration * m_pFile->GetTimeScale())
|
|
1287 / m_pTimeScaleProperty->GetValue();
|
|
1288 }
|
|
1289
|
|
1290 void MP4Track::UpdateModificationTimes()
|
|
1291 {
|
|
1292 // update media and track modification times
|
|
1293 MP4Timestamp now = MP4GetAbsTimestamp();
|
|
1294 m_pMediaModificationProperty->SetValue(now);
|
|
1295 m_pTrackModificationProperty->SetValue(now);
|
|
1296 }
|
|
1297
|
|
1298 u_int32_t MP4Track::GetNumberOfChunks()
|
|
1299 {
|
|
1300 return m_pChunkOffsetProperty->GetCount();
|
|
1301 }
|
|
1302
|
|
1303 u_int32_t MP4Track::GetChunkStscIndex(MP4ChunkId chunkId)
|
|
1304 {
|
|
1305 u_int32_t stscIndex;
|
|
1306 u_int32_t numStscs = m_pStscCountProperty->GetValue();
|
|
1307
|
|
1308 ASSERT(chunkId);
|
|
1309 ASSERT(numStscs > 0);
|
|
1310
|
|
1311 for (stscIndex = 0; stscIndex < numStscs; stscIndex++) {
|
|
1312 if (chunkId < m_pStscFirstChunkProperty->GetValue(stscIndex)) {
|
|
1313 ASSERT(stscIndex != 0);
|
|
1314 break;
|
|
1315 }
|
|
1316 }
|
|
1317 return stscIndex - 1;
|
|
1318 }
|
|
1319
|
|
1320 MP4Timestamp MP4Track::GetChunkTime(MP4ChunkId chunkId)
|
|
1321 {
|
|
1322 u_int32_t stscIndex = GetChunkStscIndex(chunkId);
|
|
1323
|
|
1324 MP4ChunkId firstChunkId =
|
|
1325 m_pStscFirstChunkProperty->GetValue(stscIndex);
|
|
1326
|
|
1327 MP4SampleId firstSample =
|
|
1328 m_pStscFirstSampleProperty->GetValue(stscIndex);
|
|
1329
|
|
1330 u_int32_t samplesPerChunk =
|
|
1331 m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
|
|
1332
|
|
1333 MP4SampleId firstSampleInChunk =
|
|
1334 firstSample + ((chunkId - firstChunkId) * samplesPerChunk);
|
|
1335
|
|
1336 MP4Timestamp chunkTime;
|
|
1337
|
|
1338 GetSampleTimes(firstSampleInChunk, &chunkTime, NULL);
|
|
1339
|
|
1340 return chunkTime;
|
|
1341 }
|
|
1342
|
|
1343 u_int32_t MP4Track::GetChunkSize(MP4ChunkId chunkId)
|
|
1344 {
|
|
1345 u_int32_t stscIndex = GetChunkStscIndex(chunkId);
|
|
1346
|
|
1347 MP4ChunkId firstChunkId =
|
|
1348 m_pStscFirstChunkProperty->GetValue(stscIndex);
|
|
1349
|
|
1350 MP4SampleId firstSample =
|
|
1351 m_pStscFirstSampleProperty->GetValue(stscIndex);
|
|
1352
|
|
1353 u_int32_t samplesPerChunk =
|
|
1354 m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
|
|
1355
|
|
1356 MP4SampleId firstSampleInChunk =
|
|
1357 firstSample + ((chunkId - firstChunkId) * samplesPerChunk);
|
|
1358
|
|
1359 // need cumulative sizes of samples in chunk
|
|
1360 u_int32_t chunkSize = 0;
|
|
1361 for (u_int32_t i = 0; i < samplesPerChunk; i++) {
|
|
1362 chunkSize += GetSampleSize(firstSampleInChunk + i);
|
|
1363 }
|
|
1364
|
|
1365 return chunkSize;
|
|
1366 }
|
|
1367
|
|
1368 void MP4Track::ReadChunk(MP4ChunkId chunkId,
|
|
1369 u_int8_t** ppChunk, u_int32_t* pChunkSize)
|
|
1370 {
|
|
1371 ASSERT(chunkId);
|
|
1372 ASSERT(ppChunk);
|
|
1373 ASSERT(pChunkSize);
|
|
1374
|
|
1375 u_int64_t chunkOffset =
|
|
1376 m_pChunkOffsetProperty->GetValue(chunkId - 1);
|
|
1377
|
|
1378 *pChunkSize = GetChunkSize(chunkId);
|
|
1379 *ppChunk = (u_int8_t*)MP4Malloc(*pChunkSize);
|
|
1380
|
|
1381 VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
|
|
1382 printf("ReadChunk: track %u id %u offset 0x"X64" size %u (0x%x)\n",
|
|
1383 m_trackId, chunkId, chunkOffset, *pChunkSize, *pChunkSize));
|
|
1384
|
|
1385 u_int64_t oldPos = m_pFile->GetPosition(); // only used in mode == 'w'
|
|
1386 try {
|
|
1387 m_pFile->SetPosition(chunkOffset);
|
|
1388 m_pFile->ReadBytes(*ppChunk, *pChunkSize);
|
|
1389 }
|
|
1390 catch (MP4Error* e) {
|
|
1391 // let's not leak memory
|
|
1392 MP4Free(*ppChunk);
|
|
1393 *ppChunk = NULL;
|
|
1394
|
|
1395 if (m_pFile->GetMode() == 'w') {
|
|
1396 m_pFile->SetPosition(oldPos);
|
|
1397 }
|
|
1398 throw e;
|
|
1399 }
|
|
1400
|
|
1401 if (m_pFile->GetMode() == 'w') {
|
|
1402 m_pFile->SetPosition(oldPos);
|
|
1403 }
|
|
1404 }
|
|
1405
|
|
1406 void MP4Track::RewriteChunk(MP4ChunkId chunkId,
|
|
1407 u_int8_t* pChunk, u_int32_t chunkSize)
|
|
1408 {
|
|
1409 u_int64_t chunkOffset = m_pFile->GetPosition();
|
|
1410
|
|
1411 m_pFile->WriteBytes(pChunk, chunkSize);
|
|
1412
|
|
1413 m_pChunkOffsetProperty->SetValue(chunkOffset, chunkId - 1);
|
|
1414
|
|
1415 VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
|
|
1416 printf("RewriteChunk: track %u id %u offset 0x"X64" size %u (0x%x)\n",
|
|
1417 m_trackId, chunkId, chunkOffset, chunkSize, chunkSize));
|
|
1418 }
|
|
1419
|
|
1420 // map track type name aliases to official names
|
|
1421
|
|
1422
|
|
1423 bool MP4Track::InitEditListProperties()
|
|
1424 {
|
|
1425 m_pElstCountProperty = NULL;
|
|
1426 m_pElstMediaTimeProperty = NULL;
|
|
1427 m_pElstDurationProperty = NULL;
|
|
1428 m_pElstRateProperty = NULL;
|
|
1429 m_pElstReservedProperty = NULL;
|
|
1430
|
|
1431 MP4Atom* pElstAtom =
|
|
1432 m_pTrakAtom->FindAtom("trak.edts.elst");
|
|
1433
|
|
1434 if (!pElstAtom) {
|
|
1435 return false;
|
|
1436 }
|
|
1437
|
|
1438 pElstAtom->FindProperty(
|
|
1439 "elst.entryCount",
|
|
1440 (MP4Property**)&m_pElstCountProperty);
|
|
1441
|
|
1442 pElstAtom->FindProperty(
|
|
1443 "elst.entries.mediaTime",
|
|
1444 (MP4Property**)&m_pElstMediaTimeProperty);
|
|
1445
|
|
1446 pElstAtom->FindProperty(
|
|
1447 "elst.entries.segmentDuration",
|
|
1448 (MP4Property**)&m_pElstDurationProperty);
|
|
1449
|
|
1450 pElstAtom->FindProperty(
|
|
1451 "elst.entries.mediaRate",
|
|
1452 (MP4Property**)&m_pElstRateProperty);
|
|
1453
|
|
1454 pElstAtom->FindProperty(
|
|
1455 "elst.entries.reserved",
|
|
1456 (MP4Property**)&m_pElstReservedProperty);
|
|
1457
|
|
1458 return m_pElstCountProperty
|
|
1459 && m_pElstMediaTimeProperty
|
|
1460 && m_pElstDurationProperty
|
|
1461 && m_pElstRateProperty
|
|
1462 && m_pElstReservedProperty;
|
|
1463 }
|
|
1464
|
|
1465 MP4EditId MP4Track::AddEdit(MP4EditId editId)
|
|
1466 {
|
|
1467 if (!m_pElstCountProperty) {
|
|
1468 m_pFile->AddDescendantAtoms(m_pTrakAtom, "edts.elst");
|
|
1469 InitEditListProperties();
|
|
1470 }
|
|
1471
|
|
1472 if (editId == MP4_INVALID_EDIT_ID) {
|
|
1473 editId = m_pElstCountProperty->GetValue() + 1;
|
|
1474 }
|
|
1475
|
|
1476 m_pElstMediaTimeProperty->InsertValue(0, editId - 1);
|
|
1477 m_pElstDurationProperty->InsertValue(0, editId - 1);
|
|
1478 m_pElstRateProperty->InsertValue(1, editId - 1);
|
|
1479 m_pElstReservedProperty->InsertValue(0, editId - 1);
|
|
1480
|
|
1481 m_pElstCountProperty->IncrementValue();
|
|
1482
|
|
1483 return editId;
|
|
1484 }
|
|
1485
|
|
1486 void MP4Track::DeleteEdit(MP4EditId editId)
|
|
1487 {
|
|
1488 if (editId == MP4_INVALID_EDIT_ID) {
|
|
1489 throw new MP4Error("edit id can't be zero",
|
|
1490 "MP4Track::DeleteEdit");
|
|
1491 }
|
|
1492
|
|
1493 if (!m_pElstCountProperty
|
|
1494 || m_pElstCountProperty->GetValue() == 0) {
|
|
1495 throw new MP4Error("no edits exist",
|
|
1496 "MP4Track::DeleteEdit");
|
|
1497 }
|
|
1498
|
|
1499 m_pElstMediaTimeProperty->DeleteValue(editId - 1);
|
|
1500 m_pElstDurationProperty->DeleteValue(editId - 1);
|
|
1501 m_pElstRateProperty->DeleteValue(editId - 1);
|
|
1502 m_pElstReservedProperty->DeleteValue(editId - 1);
|
|
1503
|
|
1504 m_pElstCountProperty->IncrementValue(-1);
|
|
1505
|
|
1506 // clean up if last edit is deleted
|
|
1507 if (m_pElstCountProperty->GetValue() == 0) {
|
|
1508 m_pElstCountProperty = NULL;
|
|
1509 m_pElstMediaTimeProperty = NULL;
|
|
1510 m_pElstDurationProperty = NULL;
|
|
1511 m_pElstRateProperty = NULL;
|
|
1512 m_pElstReservedProperty = NULL;
|
|
1513
|
|
1514 m_pTrakAtom->DeleteChildAtom(
|
|
1515 m_pTrakAtom->FindAtom("trak.edts"));
|
|
1516 }
|
|
1517 }
|
|
1518
|
|
1519 MP4Timestamp MP4Track::GetEditStart(
|
|
1520 MP4EditId editId)
|
|
1521 {
|
|
1522 if (editId == MP4_INVALID_EDIT_ID) {
|
|
1523 return MP4_INVALID_TIMESTAMP;
|
|
1524 } else if (editId == 1) {
|
|
1525 return 0;
|
|
1526 }
|
|
1527 return (MP4Timestamp)GetEditTotalDuration(editId - 1);
|
|
1528 }
|
|
1529
|
|
1530 MP4Duration MP4Track::GetEditTotalDuration(
|
|
1531 MP4EditId editId)
|
|
1532 {
|
|
1533 u_int32_t numEdits = 0;
|
|
1534
|
|
1535 if (m_pElstCountProperty) {
|
|
1536 numEdits = m_pElstCountProperty->GetValue();
|
|
1537 }
|
|
1538
|
|
1539 if (editId == MP4_INVALID_EDIT_ID) {
|
|
1540 editId = numEdits;
|
|
1541 }
|
|
1542
|
|
1543 if (numEdits == 0 || editId > numEdits) {
|
|
1544 return MP4_INVALID_DURATION;
|
|
1545 }
|
|
1546
|
|
1547 MP4Duration totalDuration = 0;
|
|
1548
|
|
1549 for (MP4EditId eid = 1; eid <= editId; eid++) {
|
|
1550 totalDuration +=
|
|
1551 m_pElstDurationProperty->GetValue(eid - 1);
|
|
1552 }
|
|
1553
|
|
1554 return totalDuration;
|
|
1555 }
|
|
1556
|
|
1557 MP4SampleId MP4Track::GetSampleIdFromEditTime(
|
|
1558 MP4Timestamp editWhen,
|
|
1559 MP4Timestamp* pStartTime,
|
|
1560 MP4Duration* pDuration)
|
|
1561 {
|
|
1562 MP4SampleId sampleId = MP4_INVALID_SAMPLE_ID;
|
|
1563 u_int32_t numEdits = 0;
|
|
1564
|
|
1565 if (m_pElstCountProperty) {
|
|
1566 numEdits = m_pElstCountProperty->GetValue();
|
|
1567 }
|
|
1568
|
|
1569 if (numEdits) {
|
|
1570 MP4Duration editElapsedDuration = 0;
|
|
1571
|
|
1572 for (MP4EditId editId = 1; editId <= numEdits; editId++) {
|
|
1573 // remember edit segment's start time (in edit timeline)
|
|
1574 MP4Timestamp editStartTime =
|
|
1575 (MP4Timestamp)editElapsedDuration;
|
|
1576
|
|
1577 // accumulate edit segment's duration
|
|
1578 editElapsedDuration +=
|
|
1579 m_pElstDurationProperty->GetValue(editId - 1);
|
|
1580
|
|
1581 // calculate difference between the specified edit time
|
|
1582 // and the end of this edit segment
|
|
1583 if (editElapsedDuration - editWhen <= 0) {
|
|
1584 // the specified time has not yet been reached
|
|
1585 continue;
|
|
1586 }
|
|
1587
|
|
1588 // 'editWhen' is within this edit segment
|
|
1589
|
|
1590 // calculate the specified edit time
|
|
1591 // relative to just this edit segment
|
|
1592 MP4Duration editOffset =
|
|
1593 editWhen - editStartTime;
|
|
1594
|
|
1595 // calculate the media (track) time that corresponds
|
|
1596 // to the specified edit time based on the edit list
|
|
1597 MP4Timestamp mediaWhen =
|
|
1598 m_pElstMediaTimeProperty->GetValue(editId - 1)
|
|
1599 + editOffset;
|
|
1600
|
|
1601 // lookup the sample id for the media time
|
|
1602 sampleId = GetSampleIdFromTime(mediaWhen, false);
|
|
1603
|
|
1604 // lookup the sample's media start time and duration
|
|
1605 MP4Timestamp sampleStartTime;
|
|
1606 MP4Duration sampleDuration;
|
|
1607
|
|
1608 GetSampleTimes(sampleId, &sampleStartTime, &sampleDuration);
|
|
1609
|
|
1610 // calculate the difference if any between when the sample
|
|
1611 // would naturally start and when it starts in the edit timeline
|
|
1612 MP4Duration sampleStartOffset =
|
|
1613 mediaWhen - sampleStartTime;
|
|
1614
|
|
1615 // calculate the start time for the sample in the edit time line
|
|
1616 MP4Timestamp editSampleStartTime =
|
|
1617 editWhen - MIN(editOffset, sampleStartOffset);
|
|
1618
|
|
1619 MP4Duration editSampleDuration = 0;
|
|
1620
|
|
1621 // calculate how long this sample lasts in the edit list timeline
|
|
1622 if (m_pElstRateProperty->GetValue(editId - 1) == 0) {
|
|
1623 // edit segment is a "dwell"
|
|
1624 // so sample duration is that of the edit segment
|
|
1625 editSampleDuration =
|
|
1626 m_pElstDurationProperty->GetValue(editId - 1);
|
|
1627
|
|
1628 } else {
|
|
1629 // begin with the natural sample duration
|
|
1630 editSampleDuration = sampleDuration;
|
|
1631
|
|
1632 // now shorten that if the edit segment starts
|
|
1633 // after the sample would naturally start
|
|
1634 if (editOffset < sampleStartOffset) {
|
|
1635 editSampleDuration -= sampleStartOffset - editOffset;
|
|
1636 }
|
|
1637
|
|
1638 // now shorten that if the edit segment ends
|
|
1639 // before the sample would naturally end
|
|
1640 if (editElapsedDuration
|
|
1641 < editSampleStartTime + sampleDuration) {
|
|
1642 editSampleDuration -= (editSampleStartTime + sampleDuration)
|
|
1643 - editElapsedDuration;
|
|
1644 }
|
|
1645 }
|
|
1646
|
|
1647 if (pStartTime) {
|
|
1648 *pStartTime = editSampleStartTime;
|
|
1649 }
|
|
1650
|
|
1651 if (pDuration) {
|
|
1652 *pDuration = editSampleDuration;
|
|
1653 }
|
|
1654
|
|
1655 VERBOSE_EDIT(m_pFile->GetVerbosity(),
|
|
1656 printf("GetSampleIdFromEditTime: when "U64" "
|
|
1657 "sampleId %u start "U64" duration "D64"\n",
|
|
1658 editWhen, sampleId,
|
|
1659 editSampleStartTime, editSampleDuration));
|
|
1660
|
|
1661 return sampleId;
|
|
1662 }
|
|
1663
|
|
1664 throw new MP4Error("time out of range",
|
|
1665 "MP4Track::GetSampleIdFromEditTime");
|
|
1666
|
|
1667 } else { // no edit list
|
|
1668 sampleId = GetSampleIdFromTime(editWhen, false);
|
|
1669
|
|
1670 if (pStartTime || pDuration) {
|
|
1671 GetSampleTimes(sampleId, pStartTime, pDuration);
|
|
1672 }
|
|
1673 }
|
|
1674
|
|
1675 return sampleId;
|
|
1676 }
|
|
1677
|
|
1678 void MP4Track::CalculateBytesPerSample ()
|
|
1679 {
|
|
1680 MP4Atom *pMedia = m_pTrakAtom->FindAtom("trak.mdia.minf.stbl.stsd");
|
|
1681 MP4Atom *pMediaData;
|
|
1682 const char *media_data_name;
|
|
1683 if (pMedia == NULL) return;
|
|
1684
|
|
1685 if (pMedia->GetNumberOfChildAtoms() != 1) return;
|
|
1686
|
|
1687 pMediaData = pMedia->GetChildAtom(0);
|
|
1688 media_data_name = pMediaData->GetType();
|
|
1689 if ((ATOMID(media_data_name) == ATOMID("twos")) ||
|
|
1690 (ATOMID(media_data_name) == ATOMID("sowt"))) {
|
|
1691 MP4IntegerProperty *chan, *sampleSize;
|
|
1692 chan = (MP4IntegerProperty *)pMediaData->GetProperty(4);
|
|
1693 sampleSize = (MP4IntegerProperty *)pMediaData->GetProperty(5);
|
|
1694 m_bytesPerSample = chan->GetValue() * (sampleSize->GetValue() / 8);
|
|
1695 }
|
|
1696 }
|
|
1697
|