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 | // A class that encapsulates an Ogg file. |
19 | // Implementation |
20 | |
21 | #include "OggFileParser.hh" |
22 | #include "OggDemuxedTrack.hh" |
23 | #include "ByteStreamFileSource.hh" |
24 | #include "VorbisAudioRTPSink.hh" |
25 | #include "SimpleRTPSink.hh" |
26 | #include "TheoraVideoRTPSink.hh" |
27 | |
28 | ////////// OggTrackTable definition ///////// |
29 | |
30 | // For looking up and iterating over the file's tracks: |
31 | |
32 | class OggTrackTable { |
33 | public: |
34 | OggTrackTable(); |
35 | virtual ~OggTrackTable(); |
36 | |
37 | void add(OggTrack* newTrack); |
38 | OggTrack* lookup(u_int32_t trackNumber); |
39 | |
40 | unsigned numTracks() const; |
41 | |
42 | private: |
43 | friend class OggTrackTableIterator; |
44 | HashTable* fTable; |
45 | }; |
46 | |
47 | |
48 | ////////// OggFile implementation ////////// |
49 | |
50 | void OggFile::createNew(UsageEnvironment& env, char const* fileName, |
51 | onCreationFunc* onCreation, void* onCreationClientData) { |
52 | new OggFile(env, fileName, onCreation, onCreationClientData); |
53 | } |
54 | |
55 | OggTrack* OggFile::lookup(u_int32_t trackNumber) { |
56 | return fTrackTable->lookup(trackNumber); |
57 | } |
58 | |
59 | OggDemux* OggFile::newDemux() { |
60 | OggDemux* demux = new OggDemux(*this); |
61 | fDemuxesTable->Add((char const*)demux, demux); |
62 | |
63 | return demux; |
64 | } |
65 | |
66 | unsigned OggFile::numTracks() const { |
67 | return fTrackTable->numTracks(); |
68 | } |
69 | |
70 | FramedSource* OggFile |
71 | ::createSourceForStreaming(FramedSource* baseSource, u_int32_t trackNumber, |
72 | unsigned& estBitrate, unsigned& numFiltersInFrontOfTrack) { |
73 | if (baseSource == NULL) return NULL; |
74 | |
75 | FramedSource* result = baseSource; // by default |
76 | numFiltersInFrontOfTrack = 0; // by default |
77 | |
78 | // Look at the track's MIME type to set its estimated bitrate (for use by RTCP). |
79 | // (Later, try to be smarter about figuring out the bitrate.) ##### |
80 | // Some MIME types also require adding a special 'framer' in front of the source. |
81 | OggTrack* track = lookup(trackNumber); |
82 | if (track != NULL) { // should always be true |
83 | estBitrate = track->estBitrate; |
84 | } |
85 | |
86 | return result; |
87 | } |
88 | |
89 | RTPSink* OggFile |
90 | ::createRTPSinkForTrackNumber(u_int32_t trackNumber, Groupsock* rtpGroupsock, |
91 | unsigned char rtpPayloadTypeIfDynamic) { |
92 | OggTrack* track = lookup(trackNumber); |
93 | if (track == NULL || track->mimeType == NULL) return NULL; |
94 | |
95 | RTPSink* result = NULL; // default value for unknown media types |
96 | |
97 | if (strcmp(track->mimeType, "audio/VORBIS" ) == 0) { |
98 | // For Vorbis audio, we use the special "identification", "comment", and "setup" headers |
99 | // that we read when we initially read the headers at the start of the file: |
100 | result = VorbisAudioRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
101 | track->samplingFrequency, track->numChannels, |
102 | track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], |
103 | track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], |
104 | track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); |
105 | } else if (strcmp(track->mimeType, "audio/OPUS" ) == 0) { |
106 | result = SimpleRTPSink |
107 | ::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
108 | 48000, "audio" , "OPUS" , 2, False/*only 1 Opus 'packet' in each RTP packet*/); |
109 | } else if (strcmp(track->mimeType, "video/THEORA" ) == 0) { |
110 | // For Theora video, we use the special "identification", "comment", and "setup" headers |
111 | // that we read when we initially read the headers at the start of the file: |
112 | result = TheoraVideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic, |
113 | track->vtoHdrs.header[0], track->vtoHdrs.headerSize[0], |
114 | track->vtoHdrs.header[1], track->vtoHdrs.headerSize[1], |
115 | track->vtoHdrs.header[2], track->vtoHdrs.headerSize[2]); |
116 | } |
117 | |
118 | return result; |
119 | } |
120 | |
121 | |
122 | OggFile::OggFile(UsageEnvironment& env, char const* fileName, |
123 | onCreationFunc* onCreation, void* onCreationClientData) |
124 | : Medium(env), |
125 | fFileName(strDup(fileName)), |
126 | fOnCreation(onCreation), fOnCreationClientData(onCreationClientData) { |
127 | fTrackTable = new OggTrackTable; |
128 | fDemuxesTable = HashTable::create(ONE_WORD_HASH_KEYS); |
129 | |
130 | FramedSource* inputSource = ByteStreamFileSource::createNew(envir(), fileName); |
131 | if (inputSource == NULL) { |
132 | // The specified input file does not exist! |
133 | fParserForInitialization = NULL; |
134 | handleEndOfBosPageParsing(); // we have no file, and thus no tracks, but we still need to signal this |
135 | } else { |
136 | // Initialize ourselves by parsing the file's headers: |
137 | fParserForInitialization |
138 | = new OggFileParser(*this, inputSource, handleEndOfBosPageParsing, this); |
139 | } |
140 | } |
141 | |
142 | OggFile::~OggFile() { |
143 | delete fParserForInitialization; |
144 | |
145 | // Delete any outstanding "OggDemux"s, and the table for them: |
146 | OggDemux* demux; |
147 | while ((demux = (OggDemux*)fDemuxesTable->RemoveNext()) != NULL) { |
148 | delete demux; |
149 | } |
150 | delete fDemuxesTable; |
151 | delete fTrackTable; |
152 | |
153 | delete[] (char*)fFileName; |
154 | } |
155 | |
156 | void OggFile::handleEndOfBosPageParsing(void* clientData) { |
157 | ((OggFile*)clientData)->handleEndOfBosPageParsing(); |
158 | } |
159 | |
160 | void OggFile::handleEndOfBosPageParsing() { |
161 | // Delete our parser, because it's done its job now: |
162 | delete fParserForInitialization; fParserForInitialization = NULL; |
163 | |
164 | // Finally, signal our caller that we've been created and initialized: |
165 | if (fOnCreation != NULL) (*fOnCreation)(this, fOnCreationClientData); |
166 | } |
167 | |
168 | void OggFile::addTrack(OggTrack* newTrack) { |
169 | fTrackTable->add(newTrack); |
170 | } |
171 | |
172 | void OggFile::removeDemux(OggDemux* demux) { |
173 | fDemuxesTable->Remove((char const*)demux); |
174 | } |
175 | |
176 | |
177 | ////////// OggTrackTable implementation ///////// |
178 | |
179 | OggTrackTable::OggTrackTable() |
180 | : fTable(HashTable::create(ONE_WORD_HASH_KEYS)) { |
181 | } |
182 | |
183 | OggTrackTable::~OggTrackTable() { |
184 | // Remove and delete all of our "OggTrack" descriptors, and the hash table itself: |
185 | OggTrack* track; |
186 | while ((track = (OggTrack*)fTable->RemoveNext()) != NULL) { |
187 | delete track; |
188 | } |
189 | delete fTable; |
190 | } |
191 | |
192 | void OggTrackTable::add(OggTrack* newTrack) { |
193 | OggTrack* existingTrack |
194 | = (OggTrack*)fTable->Add((char const*)newTrack->trackNumber, newTrack); |
195 | delete existingTrack; // if any |
196 | } |
197 | |
198 | OggTrack* OggTrackTable::lookup(u_int32_t trackNumber) { |
199 | return (OggTrack*)fTable->Lookup((char const*)trackNumber); |
200 | } |
201 | |
202 | unsigned OggTrackTable::numTracks() const { return fTable->numEntries(); } |
203 | |
204 | OggTrackTableIterator::OggTrackTableIterator(OggTrackTable& ourTable) { |
205 | fIter = HashTable::Iterator::create(*(ourTable.fTable)); |
206 | } |
207 | |
208 | OggTrackTableIterator::~OggTrackTableIterator() { |
209 | delete fIter; |
210 | } |
211 | |
212 | OggTrack* OggTrackTableIterator::next() { |
213 | char const* key; |
214 | return (OggTrack*)fIter->next(key); |
215 | } |
216 | |
217 | |
218 | ////////// OggTrack implementation ////////// |
219 | |
220 | OggTrack::OggTrack() |
221 | : trackNumber(0), mimeType(NULL), |
222 | samplingFrequency(48000), numChannels(2), estBitrate(100) { // default settings |
223 | vtoHdrs.header[0] = vtoHdrs.header[1] = vtoHdrs.header[2] = NULL; |
224 | vtoHdrs.headerSize[0] = vtoHdrs.headerSize[1] = vtoHdrs.headerSize[2] = 0; |
225 | |
226 | vtoHdrs.vorbis_mode_count = 0; |
227 | vtoHdrs.vorbis_mode_blockflag = NULL; |
228 | } |
229 | |
230 | OggTrack::~OggTrack() { |
231 | delete[] vtoHdrs.header[0]; delete[] vtoHdrs.header[1]; delete[] vtoHdrs.header[2]; |
232 | delete[] vtoHdrs.vorbis_mode_blockflag; |
233 | } |
234 | |
235 | |
236 | ///////// OggDemux implementation ///////// |
237 | |
238 | FramedSource* OggDemux::newDemuxedTrack(u_int32_t& resultTrackNumber) { |
239 | OggTrack* nextTrack; |
240 | do { |
241 | nextTrack = fIter->next(); |
242 | } while (nextTrack != NULL && nextTrack->mimeType == NULL); |
243 | |
244 | if (nextTrack == NULL) { // no more tracks |
245 | resultTrackNumber = 0; |
246 | return NULL; |
247 | } |
248 | |
249 | resultTrackNumber = nextTrack->trackNumber; |
250 | FramedSource* trackSource = new OggDemuxedTrack(envir(), resultTrackNumber, *this); |
251 | fDemuxedTracksTable->Add((char const*)resultTrackNumber, trackSource); |
252 | return trackSource; |
253 | } |
254 | |
255 | FramedSource* OggDemux::newDemuxedTrackByTrackNumber(unsigned trackNumber) { |
256 | if (trackNumber == 0) return NULL; |
257 | |
258 | FramedSource* trackSource = new OggDemuxedTrack(envir(), trackNumber, *this); |
259 | fDemuxedTracksTable->Add((char const*)trackNumber, trackSource); |
260 | return trackSource; |
261 | } |
262 | |
263 | OggDemuxedTrack* OggDemux::lookupDemuxedTrack(u_int32_t trackNumber) { |
264 | return (OggDemuxedTrack*)fDemuxedTracksTable->Lookup((char const*)trackNumber); |
265 | } |
266 | |
267 | OggDemux::OggDemux(OggFile& ourFile) |
268 | : Medium(ourFile.envir()), |
269 | fOurFile(ourFile), fDemuxedTracksTable(HashTable::create(ONE_WORD_HASH_KEYS)), |
270 | fIter(new OggTrackTableIterator(*fOurFile.fTrackTable)) { |
271 | FramedSource* fileSource = ByteStreamFileSource::createNew(envir(), ourFile.fileName()); |
272 | fOurParser = new OggFileParser(ourFile, fileSource, handleEndOfFile, this, this); |
273 | } |
274 | |
275 | OggDemux::~OggDemux() { |
276 | // Begin by acting as if we've reached the end of the source file. |
277 | // This should cause all of our demuxed tracks to get closed. |
278 | handleEndOfFile(); |
279 | |
280 | // Then delete our table of "OggDemuxedTrack"s |
281 | // - but not the "OggDemuxedTrack"s themselves; that should have already happened: |
282 | delete fDemuxedTracksTable; |
283 | |
284 | delete fIter; |
285 | delete fOurParser; |
286 | fOurFile.removeDemux(this); |
287 | } |
288 | |
289 | void OggDemux::removeTrack(u_int32_t trackNumber) { |
290 | fDemuxedTracksTable->Remove((char const*)trackNumber); |
291 | if (fDemuxedTracksTable->numEntries() == 0) { |
292 | // We no longer have any demuxed tracks, so delete ourselves now: |
293 | delete this; |
294 | } |
295 | } |
296 | |
297 | void OggDemux::continueReading() { |
298 | fOurParser->continueParsing(); |
299 | } |
300 | |
301 | void OggDemux::handleEndOfFile(void* clientData) { |
302 | ((OggDemux*)clientData)->handleEndOfFile(); |
303 | } |
304 | |
305 | void OggDemux::handleEndOfFile() { |
306 | // Iterate through all of our 'demuxed tracks', handling 'end of input' on each one. |
307 | // Hack: Because this can cause the hash table to get modified underneath us, |
308 | // we don't call the handlers until after we've first iterated through all of the tracks. |
309 | unsigned numTracks = fDemuxedTracksTable->numEntries(); |
310 | if (numTracks == 0) return; |
311 | OggDemuxedTrack** tracks = new OggDemuxedTrack*[numTracks]; |
312 | |
313 | HashTable::Iterator* iter = HashTable::Iterator::create(*fDemuxedTracksTable); |
314 | unsigned i; |
315 | char const* trackNumber; |
316 | |
317 | for (i = 0; i < numTracks; ++i) { |
318 | tracks[i] = (OggDemuxedTrack*)iter->next(trackNumber); |
319 | } |
320 | delete iter; |
321 | |
322 | for (i = 0; i < numTracks; ++i) { |
323 | if (tracks[i] == NULL) continue; // sanity check; shouldn't happen |
324 | tracks[i]->handleClosure(); |
325 | } |
326 | |
327 | delete[] tracks; |
328 | } |
329 | |