|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 #include "MediaEncoder.h" |
|
6 #include "MediaDecoder.h" |
|
7 #include "nsIPrincipal.h" |
|
8 #include "nsMimeTypes.h" |
|
9 #include "prlog.h" |
|
10 #include "mozilla/Preferences.h" |
|
11 |
|
12 #include "OggWriter.h" |
|
13 #ifdef MOZ_OPUS |
|
14 #include "OpusTrackEncoder.h" |
|
15 |
|
16 #endif |
|
17 |
|
18 #ifdef MOZ_VORBIS |
|
19 #include "VorbisTrackEncoder.h" |
|
20 #endif |
|
21 #ifdef MOZ_WEBM_ENCODER |
|
22 #include "VorbisTrackEncoder.h" |
|
23 #include "VP8TrackEncoder.h" |
|
24 #include "WebMWriter.h" |
|
25 #endif |
|
26 #ifdef MOZ_OMX_ENCODER |
|
27 #include "OmxTrackEncoder.h" |
|
28 #include "ISOMediaWriter.h" |
|
29 #endif |
|
30 |
|
31 #ifdef LOG |
|
32 #undef LOG |
|
33 #endif |
|
34 |
|
35 #ifdef PR_LOGGING |
|
36 PRLogModuleInfo* gMediaEncoderLog; |
|
37 #define LOG(type, msg) PR_LOG(gMediaEncoderLog, type, msg) |
|
38 #else |
|
39 #define LOG(type, msg) |
|
40 #endif |
|
41 |
|
42 namespace mozilla { |
|
43 |
|
44 void |
|
45 MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, |
|
46 TrackID aID, |
|
47 TrackRate aTrackRate, |
|
48 TrackTicks aTrackOffset, |
|
49 uint32_t aTrackEvents, |
|
50 const MediaSegment& aQueuedMedia) |
|
51 { |
|
52 // Process the incoming raw track data from MediaStreamGraph, called on the |
|
53 // thread of MediaStreamGraph. |
|
54 if (mAudioEncoder && aQueuedMedia.GetType() == MediaSegment::AUDIO) { |
|
55 mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate, |
|
56 aTrackOffset, aTrackEvents, |
|
57 aQueuedMedia); |
|
58 |
|
59 } else if (mVideoEncoder && aQueuedMedia.GetType() == MediaSegment::VIDEO) { |
|
60 mVideoEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate, |
|
61 aTrackOffset, aTrackEvents, |
|
62 aQueuedMedia); |
|
63 } |
|
64 } |
|
65 |
|
66 void |
|
67 MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph) |
|
68 { |
|
69 // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event. |
|
70 LOG(PR_LOG_DEBUG, ("NotifyRemoved in [MediaEncoder].")); |
|
71 if (mAudioEncoder) { |
|
72 mAudioEncoder->NotifyRemoved(aGraph); |
|
73 } |
|
74 if (mVideoEncoder) { |
|
75 mVideoEncoder->NotifyRemoved(aGraph); |
|
76 } |
|
77 |
|
78 } |
|
79 |
|
80 /* static */ |
|
81 already_AddRefed<MediaEncoder> |
|
82 MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes) |
|
83 { |
|
84 #ifdef PR_LOGGING |
|
85 if (!gMediaEncoderLog) { |
|
86 gMediaEncoderLog = PR_NewLogModule("MediaEncoder"); |
|
87 } |
|
88 #endif |
|
89 nsAutoPtr<ContainerWriter> writer; |
|
90 nsAutoPtr<AudioTrackEncoder> audioEncoder; |
|
91 nsAutoPtr<VideoTrackEncoder> videoEncoder; |
|
92 nsRefPtr<MediaEncoder> encoder; |
|
93 nsString mimeType; |
|
94 if (!aTrackTypes) { |
|
95 LOG(PR_LOG_ERROR, ("NO TrackTypes!!!")); |
|
96 return nullptr; |
|
97 } |
|
98 #ifdef MOZ_WEBM_ENCODER |
|
99 else if (MediaEncoder::IsWebMEncoderEnabled() && |
|
100 (aMIMEType.EqualsLiteral(VIDEO_WEBM) || |
|
101 (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) { |
|
102 if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) { |
|
103 audioEncoder = new VorbisTrackEncoder(); |
|
104 NS_ENSURE_TRUE(audioEncoder, nullptr); |
|
105 } |
|
106 videoEncoder = new VP8TrackEncoder(); |
|
107 writer = new WebMWriter(aTrackTypes); |
|
108 NS_ENSURE_TRUE(writer, nullptr); |
|
109 NS_ENSURE_TRUE(videoEncoder, nullptr); |
|
110 mimeType = NS_LITERAL_STRING(VIDEO_WEBM); |
|
111 } |
|
112 #endif //MOZ_WEBM_ENCODER |
|
113 #ifdef MOZ_OMX_ENCODER |
|
114 else if (MediaEncoder::IsOMXEncoderEnabled() && |
|
115 (aMIMEType.EqualsLiteral(VIDEO_MP4) || |
|
116 (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) { |
|
117 if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) { |
|
118 audioEncoder = new OmxAACAudioTrackEncoder(); |
|
119 NS_ENSURE_TRUE(audioEncoder, nullptr); |
|
120 } |
|
121 videoEncoder = new OmxVideoTrackEncoder(); |
|
122 writer = new ISOMediaWriter(aTrackTypes); |
|
123 NS_ENSURE_TRUE(writer, nullptr); |
|
124 NS_ENSURE_TRUE(videoEncoder, nullptr); |
|
125 mimeType = NS_LITERAL_STRING(VIDEO_MP4); |
|
126 } else if (MediaEncoder::IsOMXEncoderEnabled() && |
|
127 (aMIMEType.EqualsLiteral(AUDIO_3GPP))) { |
|
128 audioEncoder = new OmxAMRAudioTrackEncoder(); |
|
129 NS_ENSURE_TRUE(audioEncoder, nullptr); |
|
130 |
|
131 writer = new ISOMediaWriter(aTrackTypes, ISOMediaWriter::TYPE_FRAG_3GP); |
|
132 NS_ENSURE_TRUE(writer, nullptr); |
|
133 mimeType = NS_LITERAL_STRING(AUDIO_3GPP); |
|
134 } |
|
135 #endif // MOZ_OMX_ENCODER |
|
136 else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() && |
|
137 (aMIMEType.EqualsLiteral(AUDIO_OGG) || |
|
138 (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK))) { |
|
139 writer = new OggWriter(); |
|
140 audioEncoder = new OpusTrackEncoder(); |
|
141 NS_ENSURE_TRUE(writer, nullptr); |
|
142 NS_ENSURE_TRUE(audioEncoder, nullptr); |
|
143 mimeType = NS_LITERAL_STRING(AUDIO_OGG); |
|
144 } |
|
145 else { |
|
146 LOG(PR_LOG_ERROR, ("Can not find any encoder to record this media stream")); |
|
147 return nullptr; |
|
148 } |
|
149 LOG(PR_LOG_DEBUG, ("Create encoder result:a[%d] v[%d] w[%d] mimeType = %s.", |
|
150 audioEncoder != nullptr, videoEncoder != nullptr, |
|
151 writer != nullptr, mimeType.get())); |
|
152 encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(), |
|
153 videoEncoder.forget(), mimeType); |
|
154 return encoder.forget(); |
|
155 } |
|
156 |
|
157 /** |
|
158 * GetEncodedData() runs as a state machine, starting with mState set to |
|
159 * GET_METADDATA, the procedure should be as follow: |
|
160 * |
|
161 * While non-stop |
|
162 * If mState is GET_METADDATA |
|
163 * Get the meta data from audio/video encoder |
|
164 * If a meta data is generated |
|
165 * Get meta data from audio/video encoder |
|
166 * Set mState to ENCODE_TRACK |
|
167 * Return the final container data |
|
168 * |
|
169 * If mState is ENCODE_TRACK |
|
170 * Get encoded track data from audio/video encoder |
|
171 * If a packet of track data is generated |
|
172 * Insert encoded track data into the container stream of writer |
|
173 * If the final container data is copied to aOutput |
|
174 * Return the copy of final container data |
|
175 * If this is the last packet of input stream |
|
176 * Set mState to ENCODE_DONE |
|
177 * |
|
178 * If mState is ENCODE_DONE or ENCODE_ERROR |
|
179 * Stop the loop |
|
180 */ |
|
181 void |
|
182 MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs, |
|
183 nsAString& aMIMEType) |
|
184 { |
|
185 MOZ_ASSERT(!NS_IsMainThread()); |
|
186 |
|
187 aMIMEType = mMIMEType; |
|
188 |
|
189 bool reloop = true; |
|
190 while (reloop) { |
|
191 switch (mState) { |
|
192 case ENCODE_METADDATA: { |
|
193 LOG(PR_LOG_DEBUG, ("ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp())); |
|
194 nsresult rv = CopyMetadataToMuxer(mAudioEncoder.get()); |
|
195 if (NS_FAILED(rv)) { |
|
196 LOG(PR_LOG_ERROR, ("Error! Fail to Set Audio Metadata")); |
|
197 break; |
|
198 } |
|
199 rv = CopyMetadataToMuxer(mVideoEncoder.get()); |
|
200 if (NS_FAILED(rv)) { |
|
201 LOG(PR_LOG_ERROR, ("Error! Fail to Set Video Metadata")); |
|
202 break; |
|
203 } |
|
204 |
|
205 rv = mWriter->GetContainerData(aOutputBufs, |
|
206 ContainerWriter::GET_HEADER); |
|
207 if (NS_FAILED(rv)) { |
|
208 LOG(PR_LOG_ERROR,("Error! writer fail to generate header!")); |
|
209 mState = ENCODE_ERROR; |
|
210 break; |
|
211 } |
|
212 LOG(PR_LOG_DEBUG, ("Finish ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp())); |
|
213 mState = ENCODE_TRACK; |
|
214 break; |
|
215 } |
|
216 |
|
217 case ENCODE_TRACK: { |
|
218 LOG(PR_LOG_DEBUG, ("ENCODE_TRACK TimeStamp = %f", GetEncodeTimeStamp())); |
|
219 EncodedFrameContainer encodedData; |
|
220 nsresult rv = NS_OK; |
|
221 rv = WriteEncodedDataToMuxer(mAudioEncoder.get()); |
|
222 if (NS_FAILED(rv)) { |
|
223 LOG(PR_LOG_ERROR, ("Error! Fail to write audio encoder data to muxer")); |
|
224 break; |
|
225 } |
|
226 LOG(PR_LOG_DEBUG, ("Audio encoded TimeStamp = %f", GetEncodeTimeStamp())); |
|
227 rv = WriteEncodedDataToMuxer(mVideoEncoder.get()); |
|
228 if (NS_FAILED(rv)) { |
|
229 LOG(PR_LOG_ERROR, ("Fail to write video encoder data to muxer")); |
|
230 break; |
|
231 } |
|
232 LOG(PR_LOG_DEBUG, ("Video encoded TimeStamp = %f", GetEncodeTimeStamp())); |
|
233 // In audio only or video only case, let unavailable track's flag to be true. |
|
234 bool isAudioCompleted = (mAudioEncoder && mAudioEncoder->IsEncodingComplete()) || !mAudioEncoder; |
|
235 bool isVideoCompleted = (mVideoEncoder && mVideoEncoder->IsEncodingComplete()) || !mVideoEncoder; |
|
236 rv = mWriter->GetContainerData(aOutputBufs, |
|
237 isAudioCompleted && isVideoCompleted ? |
|
238 ContainerWriter::FLUSH_NEEDED : 0); |
|
239 if (NS_SUCCEEDED(rv)) { |
|
240 // Successfully get the copy of final container data from writer. |
|
241 reloop = false; |
|
242 } |
|
243 mState = (mWriter->IsWritingComplete()) ? ENCODE_DONE : ENCODE_TRACK; |
|
244 LOG(PR_LOG_DEBUG, ("END ENCODE_TRACK TimeStamp = %f " |
|
245 "mState = %d aComplete %d vComplete %d", |
|
246 GetEncodeTimeStamp(), mState, isAudioCompleted, isVideoCompleted)); |
|
247 break; |
|
248 } |
|
249 |
|
250 case ENCODE_DONE: |
|
251 case ENCODE_ERROR: |
|
252 LOG(PR_LOG_DEBUG, ("MediaEncoder has been shutdown.")); |
|
253 mShutdown = true; |
|
254 reloop = false; |
|
255 break; |
|
256 default: |
|
257 MOZ_CRASH("Invalid encode state"); |
|
258 } |
|
259 } |
|
260 } |
|
261 |
|
262 nsresult |
|
263 MediaEncoder::WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder) |
|
264 { |
|
265 if (aTrackEncoder == nullptr) { |
|
266 return NS_OK; |
|
267 } |
|
268 if (aTrackEncoder->IsEncodingComplete()) { |
|
269 return NS_OK; |
|
270 } |
|
271 EncodedFrameContainer encodedVideoData; |
|
272 nsresult rv = aTrackEncoder->GetEncodedTrack(encodedVideoData); |
|
273 if (NS_FAILED(rv)) { |
|
274 // Encoding might be canceled. |
|
275 LOG(PR_LOG_ERROR, ("Error! Fail to get encoded data from video encoder.")); |
|
276 mState = ENCODE_ERROR; |
|
277 return rv; |
|
278 } |
|
279 rv = mWriter->WriteEncodedTrack(encodedVideoData, |
|
280 aTrackEncoder->IsEncodingComplete() ? |
|
281 ContainerWriter::END_OF_STREAM : 0); |
|
282 if (NS_FAILED(rv)) { |
|
283 LOG(PR_LOG_ERROR, ("Error! Fail to write encoded video track to the media container.")); |
|
284 mState = ENCODE_ERROR; |
|
285 } |
|
286 return rv; |
|
287 } |
|
288 |
|
289 nsresult |
|
290 MediaEncoder::CopyMetadataToMuxer(TrackEncoder *aTrackEncoder) |
|
291 { |
|
292 if (aTrackEncoder == nullptr) { |
|
293 return NS_OK; |
|
294 } |
|
295 nsRefPtr<TrackMetadataBase> meta = aTrackEncoder->GetMetadata(); |
|
296 if (meta == nullptr) { |
|
297 LOG(PR_LOG_ERROR, ("Error! metadata = null")); |
|
298 mState = ENCODE_ERROR; |
|
299 return NS_ERROR_ABORT; |
|
300 } |
|
301 |
|
302 nsresult rv = mWriter->SetMetadata(meta); |
|
303 if (NS_FAILED(rv)) { |
|
304 LOG(PR_LOG_ERROR, ("Error! SetMetadata fail")); |
|
305 mState = ENCODE_ERROR; |
|
306 } |
|
307 return rv; |
|
308 } |
|
309 |
|
310 #ifdef MOZ_WEBM_ENCODER |
|
311 bool |
|
312 MediaEncoder::IsWebMEncoderEnabled() |
|
313 { |
|
314 return Preferences::GetBool("media.encoder.webm.enabled"); |
|
315 } |
|
316 #endif |
|
317 |
|
318 #ifdef MOZ_OMX_ENCODER |
|
319 bool |
|
320 MediaEncoder::IsOMXEncoderEnabled() |
|
321 { |
|
322 return Preferences::GetBool("media.encoder.omx.enabled"); |
|
323 } |
|
324 #endif |
|
325 |
|
326 } |