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/
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.
12
* The Original Code is MPEG4IP.
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. 2000, 2001. All Rights Reserved.
19
* Dave Mackie dmackie@cisco.com
20
* Bill May wmay@cisco.com
24
#include "file_mp4_recorder.h"
25
#include "video_encoder.h"
26
#include "audio_encoder.h"
27
#include "text_encoder.h"
28
#include "mpeg4ip_byteswap.h"
29
#include "mp4av_h264.h"
30
#include "video_v4l_source.h"
31
//#define DEBUG_H264 1
33
int CMp4Recorder::ThreadMain(void)
38
while (stop == false && SDL_SemWait(m_myMsgQueueSemaphore) == 0) {
39
pMsg = m_myMsgQueue.get_message();
42
switch (pMsg->get_value()) {
43
case MSG_NODE_STOP_THREAD:
55
DoWriteFrame((CMediaFrame*)pMsg->get_message(dontcare));
63
while ((pMsg = m_myMsgQueue.get_message()) != NULL) {
64
error_message("recorder - had msg after stop");
65
if (pMsg->get_value() == MSG_SINK_FRAME) {
67
CMediaFrame *mf = (CMediaFrame*)pMsg->get_message(dontcare);
68
if (mf->RemoveReference()) {
74
CHECK_AND_FREE(m_videoTempBuffer);
75
m_videoTempBufferSize = 0;
79
void CMp4Recorder::DoStartRecord()
88
m_makeIsmaCompliant = true;
90
m_audioFrameType = UNDEFINEDFRAME;
91
m_videoFrameType = UNDEFINEDFRAME;
92
m_textFrameType = UNDEFINEDFRAME;
94
if (m_stream != NULL) {
95
// recording normal file
96
m_video_profile = m_stream->GetVideoProfile();
97
m_audio_profile = m_stream->GetAudioProfile();
98
m_text_profile = m_stream->GetTextProfile();
99
m_recordVideo = m_stream->GetBoolValue(STREAM_VIDEO_ENABLED);
100
m_recordAudio = m_stream->GetBoolValue(STREAM_AUDIO_ENABLED);
101
m_recordText = m_stream->GetBoolValue(STREAM_TEXT_ENABLED);
104
m_audio_profile->GetIntegerValue(CFG_AUDIO_SAMPLE_RATE);
106
filename = m_stream->GetStringValue(STREAM_RECORD_MP4_FILE_NAME);
108
// recording raw file
109
m_recordVideo = m_pConfig->GetBoolValue(CONFIG_VIDEO_ENABLE) &&
110
m_pConfig->GetBoolValue(CONFIG_RECORD_RAW_IN_MP4_VIDEO);
111
m_recordAudio = m_pConfig->GetBoolValue(CONFIG_AUDIO_ENABLE) &&
112
m_pConfig->GetBoolValue(CONFIG_RECORD_RAW_IN_MP4_AUDIO);
113
m_recordText = false;
114
m_audioTimeScale = m_pConfig->GetIntegerValue(CONFIG_AUDIO_SAMPLE_RATE);
115
filename = m_pConfig->GetStringValue(CONFIG_RECORD_RAW_MP4_FILE_NAME);
119
m_pConfig->GetBoolValue(CONFIG_RECORD_MP4_VIDEO_TIMESCALE_USES_AUDIO))
120
m_videoTimeScale = m_audioTimeScale;
122
m_prevVideoFrame = NULL;
123
m_prevAudioFrame = NULL;
124
m_prevTextFrame = NULL;
126
m_videoTrackId = MP4_INVALID_TRACK_ID;
127
m_audioTrackId = MP4_INVALID_TRACK_ID;
128
m_textTrackId = MP4_INVALID_TRACK_ID;
130
// are we recording any video?
131
if (m_recordVideo || m_recordText) {
132
m_movieTimeScale = m_videoTimeScale;
133
} else { // just audio
134
m_movieTimeScale = m_audioTimeScale;
137
// get the mp4 file setup
139
// enable huge file mode in mp4
140
// if duration is very long or if estimated size goes over 1 GB
141
u_int64_t duration = m_pConfig->GetIntegerValue(CONFIG_APP_DURATION)
142
* m_pConfig->GetIntegerValue(CONFIG_APP_DURATION_UNITS)
145
(duration > 0xFFFFFFFF)
146
|| (m_pConfig->m_recordEstFileSize > (TO_U64(1000000000)));
147
uint32_t createFlags = 0;
149
createFlags |= MP4_CREATE_64BIT_DATA;
151
debug_message("Creating huge file - %s", hugeFile ? "yes" : "no");
152
u_int32_t verbosity =
153
MP4_DETAILS_ERROR /*DEBUG | MP4_DETAILS_WRITE_ALL */;
156
switch (m_pConfig->GetIntegerValue(CONFIG_RECORD_MP4_FILE_STATUS)) {
157
case FILE_MP4_APPEND:
158
m_mp4File = MP4Modify(filename,
161
case FILE_MP4_CREATE_NEW: {
163
const char *fname = filename;
164
if (stat(fname, &stats) == 0) {
165
// file already exists - create new one
166
size_t len = strlen(fname);
167
if (strncasecmp(fname + len - 4, ".mp4", 4) == 0) {
172
char *buffer = (char *)malloc(len + 22);
174
time_t val = time(NULL);
175
localtime_r(&val, &timeval);
176
memcpy(buffer, fname, len);
177
sprintf(buffer + len, "_%04u%02u%02u_%02u%02u%02u.mp4",
178
1900 + timeval.tm_year, timeval.tm_mon + 1, timeval.tm_mday,
179
timeval.tm_hour, timeval.tm_min, timeval.tm_sec);
180
error_message("trying file %s", buffer);
181
ret = stat(buffer, &stats);
186
m_mp4FileName = strdup(buffer);
193
case FILE_MP4_OVERWRITE:
194
m_mp4FileName = strdup(filename);
200
(m_recordAudio == false ||
201
strcasecmp(m_audio_profile->GetStringValue(CFG_AUDIO_ENCODING),
202
AUDIO_ENCODING_AMR) == 0) &&
203
(m_recordVideo == false ||
204
strcasecmp(m_video_profile->GetStringValue(CFG_VIDEO_ENCODING),
205
VIDEO_ENCODING_H263) == 0)) {
206
static char* p3gppSupportedBrands[2] = {"3gp5", "3gp4"};
208
m_mp4File = MP4CreateEx(m_mp4FileName,
213
p3gppSupportedBrands[0],
215
p3gppSupportedBrands,
216
NUM_ELEMENTS_IN_ARRAY(p3gppSupportedBrands));
218
m_mp4File = MP4Create(m_mp4FileName,
219
verbosity, createFlags);
226
MP4SetTimeScale(m_mp4File, m_movieTimeScale);
229
sprintf(buffer, "mp4live version %s %s", MPEG4IP_VERSION,
230
get_linux_video_type());
231
MP4SetMetadataTool(m_mp4File, buffer);
234
m_videoFrameNumber = 1;
235
if (m_stream == NULL) {
236
m_videoTrackId = MP4AddVideoTrack(m_mp4File,
238
MP4_INVALID_DURATION,
239
m_pConfig->m_videoWidth,
240
m_pConfig->m_videoHeight,
241
MP4_YUV12_VIDEO_TYPE);
243
if (m_videoTrackId == MP4_INVALID_TRACK_ID) {
244
error_message("can't create raw video track");
247
m_videoFrameType = YUVVIDEOFRAME;
248
MP4SetVideoProfileLevel(m_mp4File, 0xFF);
251
uint8_t videoProfile;
252
uint8_t *videoConfig;
253
uint32_t videoConfigLen;
257
get_video_mp4_fileinfo(m_video_profile,
265
if (m_videoFrameType == H263VIDEOFRAME) {
266
m_videoTrackId = MP4AddH263VideoTrack(m_mp4File,
269
m_video_profile->m_videoWidth,
270
m_video_profile->m_videoHeight,
276
m_video_profile->GetIntegerValue(CFG_VIDEO_BIT_RATE) * 1000;
277
// may need to do this at the end
278
MP4SetH263Bitrates(m_mp4File,
282
} else if (m_videoFrameType == H264VIDEOFRAME) {
283
uint8_t avcprofile, profile_compat, avclevel;
284
avcprofile = (m_video_profile->m_videoMpeg4ProfileId >> 16) & 0xff;
285
profile_compat = (m_video_profile->m_videoMpeg4ProfileId >> 8) & 0xff;
286
avclevel = (m_video_profile->m_videoMpeg4ProfileId) & 0xff;
287
debug_message("h264 track %x %x %x",
288
avcprofile, profile_compat, avclevel);
289
m_videoTrackId = MP4AddH264VideoTrack(m_mp4File,
291
MP4_INVALID_DURATION,
292
m_video_profile->m_videoWidth,
293
m_video_profile->m_videoHeight,
299
MP4SetVideoProfileLevel(m_mp4File, 0x7f);
301
m_makeIsmaCompliant = false;
302
} else if (m_videoFrameType == H261VIDEOFRAME) {
303
error_message("H.261 recording is not supported");
305
m_videoTrackId = MP4AddVideoTrack(m_mp4File,
307
MP4_INVALID_DURATION,
308
m_video_profile->m_videoWidth,
309
m_video_profile->m_videoHeight,
312
if (vIod == false) m_makeIod = false;
313
if (vIsma == false) m_makeIsmaCompliant = false;
315
if (m_videoTrackId == MP4_INVALID_TRACK_ID) {
316
error_message("can't create encoded video track");
320
MP4SetVideoProfileLevel(m_mp4File,
323
if (videoConfigLen > 0) {
324
MP4SetTrackESConfiguration(
334
m_audioFrameNumber = 1;
335
m_canRecordVideo = true;
337
if (m_stream == NULL) {
339
m_canRecordVideo = false;
340
m_audioTrackId = MP4AddAudioTrack(m_mp4File,
343
MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE);
345
if (m_audioTrackId == MP4_INVALID_TRACK_ID) {
346
error_message("can't create raw audio track");
350
MP4SetAudioProfileLevel(m_mp4File, 0xFF);
351
m_audioFrameType = PCMAUDIOFRAME;
354
bool createIod = false;
355
bool isma_compliant = false;
356
uint8_t audioProfile;
357
uint8_t *pAudioConfig;
358
uint32_t audioConfigLen;
359
m_canRecordVideo = false;
361
get_audio_mp4_fileinfo(m_audio_profile,
369
if (m_audioFrameType == AMRNBAUDIOFRAME ||
370
m_audioFrameType == AMRWBAUDIOFRAME) {
372
MP4AddAmrAudioTrack(m_mp4File,
373
m_audioFrameType == AMRNBAUDIOFRAME ?
378
m_audioFrameType == AMRWBAUDIOFRAME);
380
if (createIod == false) m_makeIod = false;
381
if (isma_compliant == false)
382
m_makeIsmaCompliant = false;
383
MP4SetAudioProfileLevel(m_mp4File, audioProfile);
384
m_audioTrackId = MP4AddAudioTrack(
387
MP4_INVALID_DURATION,
391
if (m_audioTrackId == MP4_INVALID_TRACK_ID) {
392
error_message("can't create encoded audio track");
397
MP4SetTrackESConfiguration(
407
debug_message("recording text %u", m_recordText);
409
m_textFrameNumber = 1;
410
if (m_stream == NULL) {
411
m_recordText = false;
414
m_textFrameType = get_text_mp4_fileinfo(m_text_profile, &url);
415
debug_message("text type %u", m_textFrameType);
416
if (m_textFrameType == HREFTEXTFRAME) {
417
m_textTrackId = MP4AddHrefTrack(m_mp4File, m_textTimeScale, MP4_INVALID_DURATION, url);
418
debug_message("Added text track %u", m_textTrackId);
420
m_recordText = false;
434
void CMp4Recorder::ProcessEncodedAudioFrame (CMediaFrame *pFrame)
436
if (m_audioFrameType == AMRNBAUDIOFRAME ||
437
m_audioFrameType == AMRWBAUDIOFRAME) {
438
uint8_t decMode = *(uint8_t *)pFrame->GetData();
439
m_amrMode |= 1 << ((decMode >> 3) & 0xf);
442
if (m_audioFrameNumber == 1) {
443
m_audioStartTimestamp = pFrame->GetTimestamp();
444
debug_message("record audio start "U64, m_audioStartTimestamp);
445
m_canRecordVideo = true;
446
m_prevAudioFrame = pFrame;
448
m_audioDiffTicks = 0;
449
m_audioDiffTicksTotal = 0;
450
m_audioFrameNumber++;
451
return; // wait until the next audio frame
454
Duration thisFrameDurationInTicks =
455
pFrame->GetTimestamp() - m_prevAudioFrame->GetTimestamp();
457
Duration thisFrameDurationInSamples =
458
GetTimescaleFromTicks(thisFrameDurationInTicks, m_audioTimeScale);
460
Duration elapsedTimeFromTimestamp;
461
elapsedTimeFromTimestamp = pFrame->GetTimestamp() - m_audioStartTimestamp;
463
if (thisFrameDurationInSamples > pFrame->GetDuration()) {
464
// we have a gap frame
465
// add the number of extra samples
467
(thisFrameDurationInSamples - pFrame->GetDuration());
468
error_message("adding encoded "D64" samples",
469
thisFrameDurationInSamples - pFrame->GetDuration());
471
// we have a consecutive frame
472
// Timestamp of pFrame should reflect encoded audio samples
474
m_audioSamples += pFrame->GetDuration();
476
* we don't convert the audio duration any more
477
* m_prevEncodedAudioFrame->SetDuration(audioDurationInSamples);
478
* Instead, we'll convert the video duration
483
(u_int8_t*)m_prevAudioFrame->GetData(),
484
m_prevAudioFrame->GetDataLength(),
485
m_prevAudioFrame->ConvertDuration(m_audioTimeScale));
487
m_audioFrameNumber++;
488
if (m_prevAudioFrame->RemoveReference()) {
489
delete m_prevAudioFrame;
491
m_prevAudioFrame = pFrame;
494
/******************************************************************************
495
* Process encoded video frame
496
******************************************************************************/
497
static uint32_t write_sei (uint8_t *to, const uint8_t *from, uint32_t len)
499
uint8_t *nal_write = to + 4;
500
uint32_t written_bytes = 1;
502
debug_message("sei start %u", len);
504
*nal_write++ = *from++;
507
uint32_t payload_type, payload_size, typesize, sizesize;
508
while (len >= 2 && *from != 0x80) {
509
payload_type = h264_read_sei_value(from, &typesize);
510
payload_size = h264_read_sei_value(from + typesize, &sizesize);
512
debug_message("type %u sizes %u", payload_type, payload_size);
514
uint32_t size = typesize + sizesize + payload_size;
516
switch (payload_type) {
521
// need to skip these
524
memmove(nal_write, from, size);
526
written_bytes += size;
534
if (written_bytes <= 1) {
537
to[0] = (written_bytes >> 24) & 0xff;
538
to[1] = (written_bytes >> 16) & 0xff;
539
to[2] = (written_bytes >> 8) & 0xff;
540
to[3] = written_bytes & 0xff;
542
debug_message("sei - %u bytes", written_bytes + 4);
544
return written_bytes + 4;
547
void CMp4Recorder::WriteH264Frame (CMediaFrame *pFrame,
550
bool isIFrame = false;
551
Duration rend_offset = 0;
552
rend_offset = pFrame->GetPtsTimestamp() -
553
pFrame->GetTimestamp();
554
rend_offset = GetTimescaleFromTicks(rend_offset, m_movieTimeScale);
556
h264_media_frame_t *mf = (h264_media_frame_t *)pFrame->GetData();
558
uint32_t size = mf->buffer_len + (4 * mf->nal_number);
559
if (size > m_videoTempBufferSize) {
560
m_videoTempBuffer = (uint8_t *)realloc(m_videoTempBuffer, size);
561
m_videoTempBufferSize = size;
563
uint32_t len_written = 0;
564
for (uint32_t ix = 0; ix < mf->nal_number; ix++) {
565
bool write_it = false;
566
switch (mf->nal_bufs[ix].nal_type) {
567
case H264_NAL_TYPE_SEI:
568
len_written += write_sei(m_videoTempBuffer + len_written,
569
mf->buffer + mf->nal_bufs[ix].nal_offset,
570
mf->nal_bufs[ix].nal_length);
572
case H264_NAL_TYPE_SEQ_PARAM:
573
if (mf->nal_bufs[ix].nal_length != m_videoH264SeqSize ||
574
(m_videoH264Seq != NULL &&
575
memcmp(m_videoH264Seq,
576
mf->buffer + mf->nal_bufs[ix].nal_offset,
577
m_videoH264SeqSize) != 0)) {
578
m_videoH264SeqSize = mf->nal_bufs[ix].nal_length;
580
(uint8_t *)realloc(m_videoH264Seq, m_videoH264SeqSize);
581
memcpy(m_videoH264Seq,
582
mf->buffer + mf->nal_bufs[ix].nal_offset,
584
MP4AddH264SequenceParameterSet(m_mp4File,
588
debug_message("writing seq parameter %u",mf->nal_bufs[ix].nal_length);
591
case H264_NAL_TYPE_PIC_PARAM:
592
if (mf->nal_bufs[ix].nal_length != m_videoH264PicSize ||
593
(m_videoH264Pic != NULL &&
594
memcmp(m_videoH264Pic,
595
mf->buffer + mf->nal_bufs[ix].nal_offset,
596
m_videoH264PicSize) != 0)) {
597
m_videoH264PicSize = mf->nal_bufs[ix].nal_length;
599
(uint8_t *)realloc(m_videoH264Pic, m_videoH264PicSize);
600
memcpy(m_videoH264Pic,
601
mf->buffer + mf->nal_bufs[ix].nal_offset,
603
MP4AddH264PictureParameterSet(m_mp4File,
607
debug_message("writing pic parameter %u", mf->nal_bufs[ix].nal_length);
610
case H264_NAL_TYPE_IDR_SLICE:
613
case H264_NAL_TYPE_NON_IDR_SLICE:
614
case H264_NAL_TYPE_DP_A_SLICE:
615
case H264_NAL_TYPE_DP_B_SLICE:
616
case H264_NAL_TYPE_DP_C_SLICE:
618
if (m_videoFrameNumber % 7 == 0) {
619
debug_message("drop frame");
625
case H264_NAL_TYPE_FILLER_DATA:
632
debug_message("%u h264 nal %d type %d %u write %d",
633
m_videoFrameNumber, ix, mf->nal_bufs[ix].nal_type,
634
mf->nal_bufs[ix].nal_length,
640
to_write = mf->nal_bufs[ix].nal_length;
641
m_videoTempBuffer[len_written] = (to_write >> 24) & 0xff;
642
m_videoTempBuffer[len_written + 1] = (to_write >> 16) & 0xff;
643
m_videoTempBuffer[len_written + 2] = (to_write >> 8) & 0xff;
644
m_videoTempBuffer[len_written + 3] = to_write & 0xff;
646
memcpy(m_videoTempBuffer + len_written,
647
mf->buffer + mf->nal_bufs[ix].nal_offset,
649
len_written += to_write;
653
debug_message("%u h264 write %u", m_videoFrameNumber, len_written);
655
MP4WriteSample(m_mp4File,
664
void CMp4Recorder::ProcessEncodedVideoFrame (CMediaFrame *pFrame)
666
// we drop encoded video frames until we get the first encoded audio frame
667
// after that we drop encoded video frames until we get the first I frame.
668
// we then stretch this I frame to the start of the first encoded audio
669
// frame and write it to the encoded video track
670
bool isIFrame = false;
671
uint8_t *pData, *pDataStart;
674
if (m_videoFrameNumber == 1) {
675
// wait until first audio frame before looking for the next I frame
676
if (!m_canRecordVideo) {
677
if (pFrame->RemoveReference()) delete pFrame;
681
// make sure this frame was captured after the first audio frame
683
pFrame->GetTimestamp() < m_audioStartTimestamp) {
684
if (pFrame->RemoveReference()) delete pFrame;
688
dataLen = pFrame->GetDataLength();
689
pDataStart = (uint8_t *)pFrame->GetData();
690
if (pFrame->GetType() == MPEG4VIDEOFRAME) {
691
pData = MP4AV_Mpeg4FindVop(pDataStart, dataLen);
693
error_message("Couldn't find vop header");
694
if (pFrame->RemoveReference()) delete pFrame;
698
MP4AV_Mpeg4GetVopType(pData,
699
dataLen - (pData - pDataStart));
700
if (voptype != VOP_TYPE_I) {
701
debug_message(U64" wrong vop type %d %02x %02x %02x %02x %02x",
702
pFrame->GetTimestamp(),
709
if (pFrame->RemoveReference()) delete pFrame;
712
} else if (pFrame->GetType() == H263VIDEOFRAME) {
713
// wait for an i frame
714
if ((pDataStart[4] & 0x02) != 0) {
715
if (pFrame->RemoveReference()) delete pFrame;
718
} else if (pFrame->GetType() == H264VIDEOFRAME) {
719
h264_media_frame_t *mf = (h264_media_frame_t *)pFrame->GetData();
720
bool found_idr = false;
721
for (uint32_t ix = 0;
722
found_idr == false && ix < mf->nal_number;
724
found_idr = mf->nal_bufs[ix].nal_type == H264_NAL_TYPE_IDR_SLICE;
727
debug_message("h264 nals %d found %d",
728
mf->nal_number, found_idr);
730
if (found_idr == false) {
731
if (pFrame->RemoveReference()) delete pFrame;
737
ret = MP4AV_Mpeg3FindPictHdr(pDataStart, dataLen, &ftype);
738
if (ret < 0 || ftype != 1) {
739
if (pFrame->RemoveReference()) delete pFrame;
744
debug_message("Video start ts "U64, pFrame->GetTimestamp());
746
// reset this timestamp to video's beginning
747
pFrame->SetTimestamp(m_audioStartTimestamp);
749
m_videoStartTimestamp = pFrame->GetTimestamp();
750
m_prevVideoFrame = pFrame;
751
m_videoFrameNumber++;
752
m_videoDurationTimescale = 0;
753
return; // wait until the next video frame
756
Duration videoDurationInTicks;
758
videoDurationInTicks =
759
pFrame->GetTimestamp() - m_videoStartTimestamp;
761
// at this point, we'll probably want to add in the audio drift values,
763
Duration videoDurationInTimescaleTotal;
764
videoDurationInTimescaleTotal =
765
GetTimescaleFromTicks(videoDurationInTicks, m_videoTimeScale);
767
Duration videoDurationInTimescaleFrame;
768
videoDurationInTimescaleFrame =
769
videoDurationInTimescaleTotal - m_videoDurationTimescale;
771
m_videoDurationTimescale += videoDurationInTimescaleFrame;
773
debug_message("vdit "D64" vdits "D64" frame "D64" total "D64" "D64,
774
videoDurationInTicks, videoDurationInTimescaleTotal,
775
videoDurationInTimescaleFrame, m_videoDurationTimescale,
776
GetTicksFromTimescale(m_videoDurationTimescale, 0, 0, m_videoTimeScale));
778
dataLen = m_prevVideoFrame->GetDataLength();
781
if (pFrame->GetType() == H264VIDEOFRAME) {
782
WriteH264Frame(m_prevVideoFrame, videoDurationInTimescaleFrame);
784
Duration rend_offset = 0;
785
if (pFrame->GetType() == MPEG4VIDEOFRAME) {
786
pData = MP4AV_Mpeg4FindVop((uint8_t *)m_prevVideoFrame->GetData(),
789
dataLen -= (pData - (uint8_t *)m_prevVideoFrame->GetData());
790
int vop_type = MP4AV_Mpeg4GetVopType(pData,dataLen);
791
isIFrame = (vop_type == VOP_TYPE_I);
792
rend_offset = m_prevVideoFrame->GetPtsTimestamp() -
793
m_prevVideoFrame->GetTimestamp();
794
if (rend_offset != 0 && vop_type != VOP_TYPE_B) {
795
rend_offset = GetTimescaleFromTicks(rend_offset, m_movieTimeScale);
798
debug_message("record type %d %02x %02x %02x %02x",
799
MP4AV_Mpeg4GetVopType(pData, dataLen),
806
pData = (uint8_t *)m_prevVideoFrame->GetData();
808
} else if (pFrame->GetType() == H263VIDEOFRAME) {
809
pData = (uint8_t *)m_prevVideoFrame->GetData();
810
isIFrame = ((pData[4] & 0x02) == 0);
813
(MP4AV_Mpeg4GetVopType(pData,dataLen) == VOP_TYPE_I);
814
debug_message("frame %02x %02x %02x %d", pData[2], pData[3], pData[4],
820
pData = (uint8_t *)m_prevVideoFrame->GetData();
821
ret = MP4AV_Mpeg3FindPictHdr(pData, dataLen, &ftype);
824
if (ftype == 1) isIFrame = true;
827
rend_offset = m_prevVideoFrame->GetPtsTimestamp() -
828
m_prevVideoFrame->GetTimestamp();
829
rend_offset = GetTimescaleFromTicks(rend_offset, m_movieTimeScale);
832
GetTimescaleFromTicks(m_prevVideoFrame->GetPtsTimestamp(),
835
GetTimescaleFromTicks(m_prevVideoFrame->GetTimestamp(),
847
videoDurationInTimescaleFrame,
852
m_videoFrameNumber++;
853
if (m_prevVideoFrame->RemoveReference()) {
854
delete m_prevVideoFrame;
856
m_prevVideoFrame = pFrame;
859
void CMp4Recorder::ProcessEncodedTextFrame (CMediaFrame *pFrame)
861
if (m_textFrameNumber == 1) {
862
if (m_prevTextFrame != NULL) {
863
if (m_prevTextFrame->RemoveReference()) {
864
delete m_prevTextFrame;
867
m_prevTextFrame = pFrame;
869
if (m_canRecordVideo == false) return;
872
// we can record. Set the timestamp of this frame to the audio start timestamp
873
m_prevTextFrame->SetTimestamp(m_audioStartTimestamp);
874
m_textStartTimestamp = m_audioStartTimestamp;
876
// need to work with video here, as well...
877
m_textStartTimestamp = m_prevTextFrame->GetTimestamp();
880
m_textDurationTimescale = 0;
884
Duration textDurationInTicks;
885
textDurationInTicks = pFrame->GetTimestamp() - m_textStartTimestamp;
887
Duration textDurationInTimescaleTotal;
888
textDurationInTimescaleTotal =
889
GetTimescaleFromTicks(textDurationInTicks, m_textTimeScale);
891
Duration textDurationInTimescaleFrame;
892
textDurationInTimescaleFrame =
893
textDurationInTimescaleTotal - m_textDurationTimescale;
895
m_textDurationTimescale += textDurationInTimescaleFrame;
897
MP4WriteSample(m_mp4File, m_textTrackId,
898
(uint8_t *)m_prevTextFrame->GetData(),
899
m_prevTextFrame->GetDataLength(),
900
textDurationInTimescaleFrame);
901
debug_message("wrote text frame %u", m_textFrameNumber);
904
if (m_prevTextFrame->RemoveReference()) {
905
delete m_prevTextFrame;
907
m_prevTextFrame = pFrame;
911
void CMp4Recorder::DoWriteFrame(CMediaFrame* pFrame)
913
// dispose of degenerate cases
914
if (pFrame == NULL) return;
917
if (pFrame->RemoveReference()) {
924
if ((m_stream == NULL && pFrame->GetType() == PCMAUDIOFRAME) ||
925
(m_stream != NULL && pFrame->GetType() == NETPCMAUDIOFRAME)) {
926
if (m_audioFrameNumber == 1) {
928
debug_message("First raw audio frame at "U64, pFrame->GetTimestamp());
930
m_audioStartTimestamp = pFrame->GetTimestamp();
931
m_canRecordVideo = true;
932
m_prevAudioFrame = pFrame;
933
m_audioFrameNumber++;
935
return; // wait until the next audio frame
939
Duration audioFrameSamples =
940
m_prevAudioFrame->GetDatalength() /
941
(m_pConfig->GetIntegerValue(CONFIG_AUDIO_CHANNELS) * sizeof(int16_t));
943
m_audioSamples += ======
948
Duration audioDurationInTicks =
949
pFrame->GetTimestamp() - m_prevAudioFrame->GetTimestamp();
951
MP4Duration audioDurationInSamples =
952
MP4ConvertToTrackDuration(m_mp4File, m_audioTrackId,
953
audioDurationInTicks, TimestampTicks);
955
debug_message("prev "U64" this "U64" diff samples"U64,
956
m_prevAudioFrame->GetTimestamp(),
957
pFrame->GetTimestamp(),
958
audioDurationInSamples);
961
m_prevAudioFrame->SetDuration(audioDurationInSamples);
963
#ifdef WORDS_BIGENDIAN
964
pcm = m_prevAudioFrame->GetData();
966
if (pFrame->GetType() != NETPCMAUDIOFRAME) {
967
uint32_t convert_size = m_prevAudioFrame->GetDataLength();
968
uint16_t *pdata = (uint16_t *)m_prevAudioFrame->GetData();
970
if (m_convert_pcm_size < convert_size) {
971
m_convert_pcm = (uint16_t *)realloc(m_convert_pcm, convert_size);
972
m_convert_pcm_size = convert_size;
974
convert_size /= sizeof(uint16_t);
975
for (uint32_t ix = 0; ix < convert_size; ix++) {
976
uint16_t swap = *pdata++;
977
m_convert_pcm[ix] = B2N_16(swap);
981
pcm = m_prevAudioFrame->GetData();
988
m_prevAudioFrame->GetDataLength(),
989
audioDurationInSamples);
990
// m_prevAudioFrame->ConvertDuration(m_audioTimeScale));
992
m_audioFrameNumber++;
993
if (m_prevAudioFrame->RemoveReference()) {
994
delete m_prevAudioFrame;
996
m_prevAudioFrame = pFrame;
999
} else if (pFrame->GetType() == m_audioFrameType) {
1001
ProcessEncodedAudioFrame(pFrame);
1007
if (m_recordVideo) {
1008
if (m_stream == NULL && pFrame->GetType() == YUVVIDEOFRAME) {
1009
// we drop raw video frames until we get the first raw audio frame
1011
// if we are also recording encoded video, we wait until the first I frame
1013
// we wait until the next raw video frame
1014
// in both cases, the duration of the first raw video frame is stretched
1015
// to the start of the first raw audio frame
1017
if (m_videoFrameNumber == 1) {
1018
// wait until the first raw audio frame is received
1019
if (!m_canRecordVideo) {
1020
if (pFrame->RemoveReference()) delete pFrame;
1024
// make sure this frame was captured after the first audio frame
1025
if (m_recordAudio &&
1026
pFrame->GetTimestamp() < m_audioStartTimestamp) {
1027
if (pFrame->RemoveReference()) delete pFrame;
1031
debug_message("First raw video frame at "U64, pFrame->GetTimestamp());
1033
m_videoStartTimestamp = pFrame->GetTimestamp();
1034
m_prevVideoFrame = pFrame;
1035
m_videoFrameNumber++;
1036
return; // wait until the next video frame
1039
Duration videoDurationInTicks;
1041
// the first raw video frame is stretched to the begining
1042
// of the first raw audio frame
1043
if (m_videoFrameNumber == 2 && m_recordAudio) {
1044
videoDurationInTicks =
1045
pFrame->GetTimestamp() - m_audioStartTimestamp;
1047
videoDurationInTicks =
1048
pFrame->GetTimestamp() - m_prevVideoFrame->GetTimestamp();
1051
m_prevVideoFrame->SetDuration(videoDurationInTicks);
1052
yuv_media_frame_t *pYUV = (yuv_media_frame_t *)m_prevVideoFrame->GetData();
1053
if (pYUV->y + m_pConfig->m_ySize == pYUV->u) {
1058
m_pConfig->m_yuvSize,
1059
m_prevVideoFrame->ConvertDuration(m_videoTimeScale));
1061
if (m_rawYUV == NULL) {
1062
debug_message("Mallocing %u", m_pConfig->m_yuvSize);
1063
m_rawYUV = (uint8_t *)malloc(m_pConfig->m_yuvSize);
1065
CopyYuv(pYUV->y, pYUV->u, pYUV->v,
1066
pYUV->y_stride, pYUV->uv_stride, pYUV->uv_stride,
1068
m_rawYUV + m_pConfig->m_ySize,
1069
m_rawYUV + m_pConfig->m_ySize + m_pConfig->m_uvSize,
1070
m_pConfig->m_videoWidth,
1071
m_pConfig->m_videoWidth / 2,
1072
m_pConfig->m_videoWidth / 2,
1073
m_pConfig->m_videoWidth, m_pConfig->m_videoHeight);
1074
MP4WriteSample(m_mp4File,
1077
m_pConfig->m_yuvSize,
1078
m_prevVideoFrame->ConvertDuration(m_videoTimeScale));
1082
m_videoFrameNumber++;
1083
if (m_prevVideoFrame->RemoveReference()) {
1084
delete m_prevVideoFrame;
1086
m_prevVideoFrame = pFrame;
1089
} else if (pFrame->GetType() == m_videoFrameType) {
1091
ProcessEncodedVideoFrame(pFrame);
1095
if (pFrame->GetType() == m_textFrameType && m_recordText) {
1096
ProcessEncodedTextFrame(pFrame);
1097
} else { // degenerate case
1098
if (pFrame->RemoveReference()) delete pFrame;
1102
void CMp4Recorder::DoStopRecord()
1104
if (!m_sink) return;
1106
Duration totalAudioDuration = 0;
1108
// write last audio frame
1109
if (m_prevAudioFrame) {
1113
(u_int8_t*)m_prevAudioFrame->GetData(),
1114
m_prevAudioFrame->GetDataLength(),
1115
m_prevAudioFrame->ConvertDuration(m_audioTimeScale));
1116
m_audioSamples += m_prevAudioFrame->GetDuration();
1118
totalAudioDuration = m_audioSamples;
1119
totalAudioDuration *= TimestampTicks;
1120
totalAudioDuration /= m_audioTimeScale;
1122
if (m_prevAudioFrame->RemoveReference()) {
1123
delete m_prevAudioFrame;
1125
if (m_audioFrameType == AMRNBAUDIOFRAME ||
1126
m_audioFrameType == AMRWBAUDIOFRAME) {
1127
MP4SetAmrModeSet(m_mp4File, m_audioTrackId, m_amrMode);
1131
// write last video frame
1132
if (m_prevVideoFrame) {
1133
if (m_stream == NULL) {
1135
error_message("write last raw video frame");
1140
(u_int8_t*)m_prevVideoFrame->GetData(),
1141
m_prevVideoFrame->GetDataLength(),
1142
m_prevVideoFrame->ConvertDuration(m_videoTimeScale));
1144
if (m_prevVideoFrame->RemoveReference()) {
1145
delete m_prevVideoFrame;
1147
CHECK_AND_FREE(m_rawYUV);
1150
Duration rend_offset = 0;
1152
if (m_prevVideoFrame->GetType() == H264VIDEOFRAME) {
1153
WriteH264Frame(m_prevVideoFrame,
1154
m_prevVideoFrame->ConvertDuration(m_videoTimeScale));
1156
if (m_prevVideoFrame->GetType() == MPEG4VIDEOFRAME ||
1157
m_prevVideoFrame->GetType() == H263VIDEOFRAME) {
1159
(MP4AV_Mpeg4GetVopType(
1160
(u_int8_t*) m_prevVideoFrame->GetData(),
1161
m_prevVideoFrame->GetDataLength()) == VOP_TYPE_I);
1165
MP4AV_Mpeg3FindPictHdr((u_int8_t *)m_prevVideoFrame->GetData(),
1166
m_prevVideoFrame->GetDataLength(),
1170
if (ftype == 1) isIFrame = true;
1172
rend_offset = m_prevVideoFrame->GetPtsTimestamp() -
1173
m_prevVideoFrame->GetTimestamp();
1174
rend_offset = GetTimescaleFromTicks(rend_offset, m_movieTimeScale);
1182
(u_int8_t*) m_prevVideoFrame->GetData(),
1183
m_prevVideoFrame->GetDataLength(),
1184
m_prevVideoFrame->ConvertDuration(m_videoTimeScale),
1188
if (m_prevVideoFrame->RemoveReference()) {
1189
delete m_prevVideoFrame;
1194
if (m_prevTextFrame) {
1195
Duration lastDuration = TimestampTicks;
1197
if (totalAudioDuration != 0) {
1198
Duration totalSoFar = m_prevTextFrame->GetTimestamp() - m_textStartTimestamp;
1199
if (totalAudioDuration > totalSoFar) {
1200
lastDuration = totalAudioDuration - totalSoFar;
1203
debug_message("last duration "U64" timescale "U64,
1204
lastDuration, GetTimescaleFromTicks(lastDuration, m_textTimeScale));
1205
MP4WriteSample(m_mp4File,
1207
(uint8_t *)m_prevTextFrame->GetData(),
1208
m_prevTextFrame->GetDataLength(),
1209
GetTimescaleFromTicks(lastDuration, m_textTimeScale));
1210
if (m_prevTextFrame->RemoveReference()) {
1211
delete m_prevTextFrame;
1213
m_prevTextFrame = NULL;
1216
CHECK_AND_FREE(m_videoH264Seq);
1217
m_videoH264SeqSize = 0;
1218
CHECK_AND_FREE(m_videoH264Pic);
1219
m_videoH264PicSize = 0;
1221
// close the mp4 file
1222
MP4Close(m_mp4File);
1225
debug_message("done with writing last frame");
1226
bool optimize = false;
1228
// create hint tracks
1229
if (m_pConfig->GetBoolValue(CONFIG_RECORD_MP4_HINT_TRACKS)) {
1231
m_mp4File = MP4Modify(m_mp4FileName, MP4_DETAILS_ERROR);
1233
if (m_pConfig->GetBoolValue(CONFIG_RECORD_MP4_OPTIMIZE)) {
1237
if (m_stream != NULL) {
1238
if (MP4_IS_VALID_TRACK_ID(m_videoTrackId) && m_stream) {
1239
create_mp4_video_hint_track(m_video_profile,
1242
m_pConfig->GetIntegerValue(CONFIG_RTP_PAYLOAD_SIZE));
1245
if (MP4_IS_VALID_TRACK_ID(m_audioTrackId)) {
1246
create_mp4_audio_hint_track(m_audio_profile,
1249
m_pConfig->GetIntegerValue(CONFIG_RTP_PAYLOAD_SIZE));
1251
if (MP4_IS_VALID_TRACK_ID(m_textTrackId)) {
1252
create_mp4_text_hint_track(m_text_profile,
1255
m_pConfig->GetIntegerValue(CONFIG_RTP_PAYLOAD_SIZE));
1258
if (MP4_IS_VALID_TRACK_ID(m_audioTrackId)) {
1259
L16Hinter(m_mp4File,
1261
m_pConfig->GetIntegerValue(CONFIG_RTP_PAYLOAD_SIZE));
1264
MP4Close(m_mp4File);
1267
debug_message("done with hint");
1269
// add ISMA style OD and Scene tracks
1270
if (m_stream != NULL) {
1271
if (m_pConfig->GetBoolValue(CONFIG_RECORD_MP4_ISMA_COMPLIANT)) {
1272
bool useIsmaTag = false;
1274
// if AAC track is present, can tag this as ISMA compliant content
1275
useIsmaTag = m_makeIsmaCompliant;
1277
MP4MakeIsmaCompliant(
1286
MP4Optimize(m_mp4FileName);