1 /**********
2 This library is free software; you can redistribute it and/or modify it under
3 the terms of the GNU Lesser General Public License as published by the
4 Free Software Foundation; either version 3 of the License, or (at your
5 option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)
6 
7 This library is distributed in the hope that it will be useful, but WITHOUT
8 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
10 more details.
11 
12 You should have received a copy of the GNU Lesser General Public License
13 along with this library; if not, write to the Free Software Foundation, Inc.,
14 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
15 **********/
16 // "liveMedia"
17 // Copyright (c) 1996-2020 Live Networks, Inc.  All rights reserved.
18 // 'Ogg' File Sink (recording a single media track only)
19 // Implementation
20 
21 #include "OggFileSink.hh"
22 #include "OutputFile.hh"
23 #include "VorbisAudioRTPSource.hh" // for "parseVorbisOrTheoraConfigStr()"
24 #include "MPEG2TransportStreamMultiplexor.hh" // for calculateCRC()
25 #include "FramedSource.hh"
26 
27 OggFileSink* OggFileSink
createNew(UsageEnvironment & env,char const * fileName,unsigned samplingFrequency,char const * configStr,unsigned bufferSize,Boolean oneFilePerFrame)28 ::createNew(UsageEnvironment& env, char const* fileName,
29 	    unsigned samplingFrequency, char const* configStr,
30 	    unsigned bufferSize, Boolean oneFilePerFrame) {
31   do {
32     FILE* fid;
33     char const* perFrameFileNamePrefix;
34     if (oneFilePerFrame) {
35       // Create the fid for each frame
36       fid = NULL;
37       perFrameFileNamePrefix = fileName;
38     } else {
39       // Normal case: create the fid once
40       fid = OpenOutputFile(env, fileName);
41       if (fid == NULL) break;
42       perFrameFileNamePrefix = NULL;
43     }
44 
45     return new OggFileSink(env, fid, samplingFrequency, configStr, bufferSize, perFrameFileNamePrefix);
46   } while (0);
47 
48   return NULL;
49 }
50 
OggFileSink(UsageEnvironment & env,FILE * fid,unsigned samplingFrequency,char const * configStr,unsigned bufferSize,char const * perFrameFileNamePrefix)51 OggFileSink::OggFileSink(UsageEnvironment& env, FILE* fid,
52 			 unsigned samplingFrequency, char const* configStr,
53 			 unsigned bufferSize, char const* perFrameFileNamePrefix)
54   : FileSink(env, fid, bufferSize, perFrameFileNamePrefix),
55     fSamplingFrequency(samplingFrequency), fConfigStr(strDup(configStr)),
56     fHaveWrittenFirstFrame(False), fHaveSeenEOF(False),
57     fGranulePosition(0), fGranulePositionAdjustment(0), fPageSequenceNumber(0),
58     fIsTheora(False), fGranuleIncrementPerFrame(1),
59     fAltFrameSize(0), fAltNumTruncatedBytes(0) {
60   fAltBuffer = new unsigned char[bufferSize];
61 
62   // Initialize our 'Ogg page header' array with constant values:
63   u_int8_t* p = fPageHeaderBytes;
64   *p++=0x4f; *p++=0x67; *p++=0x67; *p++=0x53; // bytes 0..3: 'capture_pattern': "OggS"
65   *p++=0; // byte 4: 'stream_structure_version': 0
66   *p++=0; // byte 5: 'header_type_flag': set on each write
67   *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0; *p++=0;
68       // bytes 6..13: 'granule_position': set on each write
69   *p++=1; *p++=0; *p++=0; *p++=0; // bytes 14..17: 'bitstream_serial_number': 1
70   *p++=0; *p++=0; *p++=0; *p++=0; // bytes 18..21: 'page_sequence_number': set on each write
71   *p++=0; *p++=0; *p++=0; *p++=0; // bytes 22..25: 'CRC_checksum': set on each write
72   *p=0; // byte 26: 'number_page_segments': set on each write
73 }
74 
~OggFileSink()75 OggFileSink::~OggFileSink() {
76   // We still have the previously-arrived frame, so write it to the file before we end:
77   fHaveSeenEOF = True;
78   OggFileSink::addData(fAltBuffer, fAltFrameSize, fAltPresentationTime);
79 
80   delete[] fAltBuffer;
81   delete[] (char*)fConfigStr;
82 }
83 
continuePlaying()84 Boolean OggFileSink::continuePlaying() {
85   // Identical to "FileSink::continuePlaying()",
86   // except that we use our own 'on source closure' function:
87   if (fSource == NULL) return False;
88 
89   fSource->getNextFrame(fBuffer, fBufferSize,
90 			FileSink::afterGettingFrame, this,
91 			ourOnSourceClosure, this);
92   return True;
93 }
94 
95 #define PAGE_DATA_MAX_SIZE (255*255)
96 
addData(unsigned char const * data,unsigned dataSize,struct timeval presentationTime)97 void OggFileSink::addData(unsigned char const* data, unsigned dataSize,
98 			  struct timeval presentationTime) {
99   if (dataSize == 0) return;
100 
101   // Set "fGranulePosition" for this frame:
102   if (fIsTheora) {
103     // Special case for Theora: "fGranulePosition" is supposed to be made up of a pair:
104     //   (frame count to last key frame) | (frame count since last key frame)
105     // However, because there appears to be no easy way to figure out which frames are key frames,
106     // we just assume that all frames are key frames.
107     if (!(data[0] >= 0x80 && data[0] <= 0x82)) { // for header pages, "fGranulePosition" remains 0
108       fGranulePosition += fGranuleIncrementPerFrame;
109     }
110   } else {
111     double ptDiff
112       = (presentationTime.tv_sec - fFirstPresentationTime.tv_sec)
113       + (presentationTime.tv_usec - fFirstPresentationTime.tv_usec)/1000000.0;
114     int64_t newGranulePosition
115       = (int64_t)(fSamplingFrequency*ptDiff) + fGranulePositionAdjustment;
116     if (newGranulePosition < fGranulePosition) {
117       // Update "fGranulePositionAdjustment" so that "fGranulePosition" remains monotonic
118       fGranulePositionAdjustment += fGranulePosition - newGranulePosition;
119     } else {
120       fGranulePosition = newGranulePosition;
121     }
122   }
123 
124   // Write the frame to the file as a single Ogg 'page' (or perhaps as multiple pages
125   // if it's too big for a single page).  We don't aggregate more than one frame within
126   // an Ogg page because that's not legal for some headers, and because that would make
127   // it difficult for us to properly set the 'eos' (end of stream) flag on the last page.
128 
129   // First, figure out how many pages to write here
130   // (a page can contain no more than PAGE_DATA_MAX_SIZE bytes)
131   unsigned numPagesToWrite = dataSize/PAGE_DATA_MAX_SIZE + 1;
132       // Note that if "dataSize" is a integral multiple of PAGE_DATA_MAX_SIZE, there will
133       // be an extra 0-size page at the end
134   for (unsigned i = 0; i < numPagesToWrite; ++i) {
135     // First, fill in the changeable parts of our 'page header' array;
136     u_int8_t header_type_flag = 0x0;
137     if (!fHaveWrittenFirstFrame && i == 0) {
138       header_type_flag |= 0x02; // 'bos'
139       fHaveWrittenFirstFrame = True; // for the future
140     }
141     if (i > 0) header_type_flag |= 0x01; // 'continuation'
142     if (fHaveSeenEOF && i == numPagesToWrite-1) header_type_flag |= 0x04; // 'eos'
143     fPageHeaderBytes[5] = header_type_flag;
144 
145     if (i < numPagesToWrite-1) {
146       // For pages where the frame does not end, set 'granule_position' in the header to -1:
147       fPageHeaderBytes[6] = fPageHeaderBytes[7] = fPageHeaderBytes[8] = fPageHeaderBytes[9] =
148       fPageHeaderBytes[10] = fPageHeaderBytes[11] = fPageHeaderBytes[12] = fPageHeaderBytes[13]
149 	= 0xFF;
150     } else {
151       fPageHeaderBytes[6] = (u_int8_t)fGranulePosition;
152       fPageHeaderBytes[7] = (u_int8_t)(fGranulePosition>>8);
153       fPageHeaderBytes[8] = (u_int8_t)(fGranulePosition>>16);
154       fPageHeaderBytes[9] = (u_int8_t)(fGranulePosition>>24);
155       fPageHeaderBytes[10] = (u_int8_t)(fGranulePosition>>32);
156       fPageHeaderBytes[11] = (u_int8_t)(fGranulePosition>>40);
157       fPageHeaderBytes[12] = (u_int8_t)(fGranulePosition>>48);
158       fPageHeaderBytes[13] = (u_int8_t)(fGranulePosition>>56);
159     }
160 
161     fPageHeaderBytes[18] = (u_int8_t)fPageSequenceNumber;
162     fPageHeaderBytes[19] = (u_int8_t)(fPageSequenceNumber>>8);
163     fPageHeaderBytes[20] = (u_int8_t)(fPageSequenceNumber>>16);
164     fPageHeaderBytes[21] = (u_int8_t)(fPageSequenceNumber>>24);
165     ++fPageSequenceNumber;
166 
167     unsigned pageDataSize;
168     u_int8_t number_page_segments;
169     if (dataSize >= PAGE_DATA_MAX_SIZE) {
170       pageDataSize = PAGE_DATA_MAX_SIZE;
171       number_page_segments = 255;
172     } else {
173       pageDataSize = dataSize;
174       number_page_segments = (pageDataSize+255)/255; // so that we don't end with a lacing of 255
175     }
176     fPageHeaderBytes[26] = number_page_segments;
177 
178     u_int8_t segment_table[255];
179     for (unsigned j = 0; j < (unsigned)(number_page_segments-1); ++j) {
180       segment_table[j] = 255;
181     }
182     segment_table[number_page_segments-1] = pageDataSize%255;
183 
184     // Compute the CRC from the 'page header' array, the 'segment_table', and the frame data:
185     u_int32_t crc = 0;
186     fPageHeaderBytes[22] = fPageHeaderBytes[23] = fPageHeaderBytes[24] = fPageHeaderBytes[25] = 0;
187     crc = calculateCRC(fPageHeaderBytes, 27, 0);
188     crc = calculateCRC(segment_table, number_page_segments, crc);
189     crc = calculateCRC(data, pageDataSize, crc);
190     fPageHeaderBytes[22] = (u_int8_t)crc;
191     fPageHeaderBytes[23] = (u_int8_t)(crc>>8);
192     fPageHeaderBytes[24] = (u_int8_t)(crc>>16);
193     fPageHeaderBytes[25] = (u_int8_t)(crc>>24);
194 
195     // Then write out the 'page header' array:
196     FileSink::addData(fPageHeaderBytes, 27, presentationTime);
197 
198     // Then write out the 'segment_table':
199     FileSink::addData(segment_table, number_page_segments, presentationTime);
200 
201     // Then add frame data, to complete the page:
202     FileSink::addData(data, pageDataSize, presentationTime);
203     data += pageDataSize;
204     dataSize -= pageDataSize;
205   }
206 }
207 
afterGettingFrame(unsigned frameSize,unsigned numTruncatedBytes,struct timeval presentationTime)208 void OggFileSink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes, struct timeval presentationTime) {
209   if (!fHaveWrittenFirstFrame) {
210     fFirstPresentationTime = presentationTime;
211 
212     // If we have a 'config string' representing 'packed configuration headers'
213     // ("identification", "comment", "setup"), unpack them and prepend them to the file:
214     if (fConfigStr != NULL && fConfigStr[0] != '\0') {
215       u_int8_t* identificationHdr; unsigned identificationHdrSize;
216       u_int8_t* commentHdr; unsigned commentHdrSize;
217       u_int8_t* setupHdr; unsigned setupHdrSize;
218       u_int32_t identField;
219       parseVorbisOrTheoraConfigStr(fConfigStr,
220 				   identificationHdr, identificationHdrSize,
221 				   commentHdr, commentHdrSize,
222 				   setupHdr, setupHdrSize,
223 				   identField);
224       if (identificationHdrSize >= 42
225 	  && strncmp((const char*)&identificationHdr[1], "theora", 6) == 0) {
226 	// Hack for Theora video: Parse the "identification" hdr to get the "KFGSHIFT" parameter:
227 	fIsTheora = True;
228 	u_int8_t const KFGSHIFT = ((identificationHdr[40]&3)<<3) | (identificationHdr[41]>>5);
229 	fGranuleIncrementPerFrame = (u_int64_t)(1 << KFGSHIFT);
230       }
231       OggFileSink::addData(identificationHdr, identificationHdrSize, presentationTime);
232       OggFileSink::addData(commentHdr, commentHdrSize, presentationTime);
233 
234       // Hack: Handle the "setup" header as if had arrived in the previous delivery, so it'll get
235       // written properly below:
236       if (setupHdrSize > fBufferSize) {
237 	fAltFrameSize = fBufferSize;
238 	fAltNumTruncatedBytes = setupHdrSize - fBufferSize;
239       } else {
240 	fAltFrameSize = setupHdrSize;
241 	fAltNumTruncatedBytes = 0;
242       }
243       memmove(fAltBuffer, setupHdr, fAltFrameSize);
244       fAltPresentationTime = presentationTime;
245 
246       delete[] identificationHdr;
247       delete[] commentHdr;
248       delete[] setupHdr;
249     }
250   }
251 
252   // Save this input frame for next time, and instead write the previous input frame now:
253   unsigned char* tmpPtr = fBuffer; fBuffer = fAltBuffer; fAltBuffer = tmpPtr;
254   unsigned prevFrameSize = fAltFrameSize; fAltFrameSize = frameSize;
255   unsigned prevNumTruncatedBytes = fAltNumTruncatedBytes; fAltNumTruncatedBytes = numTruncatedBytes;
256   struct timeval prevPresentationTime = fAltPresentationTime; fAltPresentationTime = presentationTime;
257 
258   // Call the parent class to complete the normal file write with the (previous) input frame:
259   FileSink::afterGettingFrame(prevFrameSize, prevNumTruncatedBytes, prevPresentationTime);
260 }
261 
ourOnSourceClosure(void * clientData)262 void OggFileSink::ourOnSourceClosure(void* clientData) {
263   ((OggFileSink*)clientData)->ourOnSourceClosure();
264 }
265 
ourOnSourceClosure()266 void OggFileSink::ourOnSourceClosure() {
267   fHaveSeenEOF = True;
268 
269   // We still have the previously-arrived frame, so write it to the file before we end:
270   OggFileSink::addData(fAltBuffer, fAltFrameSize, fAltPresentationTime);
271 
272   // Handle the closure for real:
273   onSourceClosure();
274 }
275