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