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 | // MP3 File Sources |
19 | // Implementation |
20 | |
21 | #include "MP3FileSource.hh" |
22 | #include "MP3StreamState.hh" |
23 | #include "InputFile.hh" |
24 | |
25 | ////////// MP3FileSource ////////// |
26 | |
27 | MP3FileSource::MP3FileSource(UsageEnvironment& env, FILE* fid) |
28 | : FramedFileSource(env, fid), |
29 | fStreamState(new MP3StreamState(env)) { |
30 | } |
31 | |
32 | MP3FileSource::~MP3FileSource() { |
33 | delete fStreamState; |
34 | } |
35 | |
36 | char const* MP3FileSource::MIMEtype() const { |
37 | return "audio/MPEG" ; |
38 | } |
39 | |
40 | MP3FileSource* MP3FileSource::createNew(UsageEnvironment& env, char const* fileName) { |
41 | MP3FileSource* newSource = NULL; |
42 | |
43 | do { |
44 | FILE* fid; |
45 | |
46 | fid = OpenInputFile(env, fileName); |
47 | if (fid == NULL) break; |
48 | |
49 | newSource = new MP3FileSource(env, fid); |
50 | if (newSource == NULL) break; |
51 | |
52 | unsigned fileSize = (unsigned)GetFileSize(fileName, fid); |
53 | newSource->assignStream(fid, fileSize); |
54 | if (!newSource->initializeStream()) break; |
55 | |
56 | return newSource; |
57 | } while (0); |
58 | |
59 | Medium::close(newSource); |
60 | return NULL; |
61 | } |
62 | |
63 | float MP3FileSource::filePlayTime() const { |
64 | return fStreamState->filePlayTime(); |
65 | } |
66 | |
67 | unsigned MP3FileSource::fileSize() const { |
68 | return fStreamState->fileSize(); |
69 | } |
70 | |
71 | void MP3FileSource::setPresentationTimeScale(unsigned scale) { |
72 | fStreamState->setPresentationTimeScale(scale); |
73 | } |
74 | |
75 | void MP3FileSource::seekWithinFile(double seekNPT, double streamDuration) { |
76 | float fileDuration = filePlayTime(); |
77 | |
78 | // First, make sure that 0.0 <= seekNPT <= seekNPT + streamDuration <= fileDuration |
79 | if (seekNPT < 0.0) { |
80 | seekNPT = 0.0; |
81 | } else if (seekNPT > fileDuration) { |
82 | seekNPT = fileDuration; |
83 | } |
84 | if (streamDuration < 0.0) { |
85 | streamDuration = 0.0; |
86 | } else if (seekNPT + streamDuration > fileDuration) { |
87 | streamDuration = fileDuration - seekNPT; |
88 | } |
89 | |
90 | float seekFraction = (float)seekNPT/fileDuration; |
91 | unsigned seekByteNumber = fStreamState->getByteNumberFromPositionFraction(seekFraction); |
92 | fStreamState->seekWithinFile(seekByteNumber); |
93 | |
94 | fLimitNumBytesToStream = False; // by default |
95 | if (streamDuration > 0.0) { |
96 | float endFraction = (float)(seekNPT + streamDuration)/fileDuration; |
97 | unsigned endByteNumber = fStreamState->getByteNumberFromPositionFraction(endFraction); |
98 | if (endByteNumber > seekByteNumber) { // sanity check |
99 | fNumBytesToStream = endByteNumber - seekByteNumber; |
100 | fLimitNumBytesToStream = True; |
101 | } |
102 | } |
103 | } |
104 | |
105 | void MP3FileSource::getAttributes() const { |
106 | char buffer[200]; |
107 | fStreamState->getAttributes(buffer, sizeof buffer); |
108 | envir().setResultMsg(buffer); |
109 | } |
110 | |
111 | void MP3FileSource::doGetNextFrame() { |
112 | if (!doGetNextFrame1()) { |
113 | handleClosure(); |
114 | return; |
115 | } |
116 | |
117 | // Switch to another task: |
118 | #if defined(__WIN32__) || defined(_WIN32) |
119 | // HACK: liveCaster/lc uses an implementation of scheduleDelayedTask() |
120 | // that performs very badly (chewing up lots of CPU time, apparently polling) |
121 | // on Windows. Until this is fixed, we just call our "afterGetting()" |
122 | // function directly. This avoids infinite recursion, as long as our sink |
123 | // is discontinuous, which is the case for the RTP sink that liveCaster/lc |
124 | // uses. ##### |
125 | afterGetting(this); |
126 | #else |
127 | nextTask() = envir().taskScheduler().scheduleDelayedTask(0, |
128 | (TaskFunc*)afterGetting, this); |
129 | #endif |
130 | } |
131 | |
132 | Boolean MP3FileSource::doGetNextFrame1() { |
133 | if (fLimitNumBytesToStream && fNumBytesToStream == 0) return False; // we've already streamed as much as we were asked for |
134 | |
135 | if (!fHaveJustInitialized) { |
136 | if (fStreamState->findNextHeader(fPresentationTime) == 0) return False; |
137 | } else { |
138 | fPresentationTime = fFirstFramePresentationTime; |
139 | fHaveJustInitialized = False; |
140 | } |
141 | |
142 | if (!fStreamState->readFrame(fTo, fMaxSize, fFrameSize, fDurationInMicroseconds)) { |
143 | char tmp[200]; |
144 | sprintf(tmp, |
145 | "Insufficient buffer size %d for reading MPEG audio frame (needed %d)\n" , |
146 | fMaxSize, fFrameSize); |
147 | envir().setResultMsg(tmp); |
148 | fFrameSize = fMaxSize; |
149 | return False; |
150 | } |
151 | if (fNumBytesToStream > fFrameSize) fNumBytesToStream -= fFrameSize; else fNumBytesToStream = 0; |
152 | |
153 | return True; |
154 | } |
155 | |
156 | void MP3FileSource::assignStream(FILE* fid, unsigned fileSize) { |
157 | fStreamState->assignStream(fid, fileSize); |
158 | } |
159 | |
160 | |
161 | Boolean MP3FileSource::initializeStream() { |
162 | // Make sure the file has an appropriate header near the start: |
163 | if (fStreamState->findNextHeader(fFirstFramePresentationTime) == 0) { |
164 | envir().setResultMsg("not an MPEG audio file" ); |
165 | return False; |
166 | } |
167 | |
168 | fStreamState->checkForXingHeader(); // in case this is a VBR file |
169 | |
170 | fHaveJustInitialized = True; |
171 | fLimitNumBytesToStream = False; |
172 | fNumBytesToStream = 0; |
173 | |
174 | // Hack: It's possible that our environment's 'result message' has been |
175 | // reset within this function, so set it again to our name now: |
176 | envir().setResultMsg(name()); |
177 | return True; |
178 | } |
179 | |