1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/encoder/MediaEncoder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,326 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 +#include "MediaEncoder.h" 1.9 +#include "MediaDecoder.h" 1.10 +#include "nsIPrincipal.h" 1.11 +#include "nsMimeTypes.h" 1.12 +#include "prlog.h" 1.13 +#include "mozilla/Preferences.h" 1.14 + 1.15 +#include "OggWriter.h" 1.16 +#ifdef MOZ_OPUS 1.17 +#include "OpusTrackEncoder.h" 1.18 + 1.19 +#endif 1.20 + 1.21 +#ifdef MOZ_VORBIS 1.22 +#include "VorbisTrackEncoder.h" 1.23 +#endif 1.24 +#ifdef MOZ_WEBM_ENCODER 1.25 +#include "VorbisTrackEncoder.h" 1.26 +#include "VP8TrackEncoder.h" 1.27 +#include "WebMWriter.h" 1.28 +#endif 1.29 +#ifdef MOZ_OMX_ENCODER 1.30 +#include "OmxTrackEncoder.h" 1.31 +#include "ISOMediaWriter.h" 1.32 +#endif 1.33 + 1.34 +#ifdef LOG 1.35 +#undef LOG 1.36 +#endif 1.37 + 1.38 +#ifdef PR_LOGGING 1.39 +PRLogModuleInfo* gMediaEncoderLog; 1.40 +#define LOG(type, msg) PR_LOG(gMediaEncoderLog, type, msg) 1.41 +#else 1.42 +#define LOG(type, msg) 1.43 +#endif 1.44 + 1.45 +namespace mozilla { 1.46 + 1.47 +void 1.48 +MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, 1.49 + TrackID aID, 1.50 + TrackRate aTrackRate, 1.51 + TrackTicks aTrackOffset, 1.52 + uint32_t aTrackEvents, 1.53 + const MediaSegment& aQueuedMedia) 1.54 +{ 1.55 + // Process the incoming raw track data from MediaStreamGraph, called on the 1.56 + // thread of MediaStreamGraph. 1.57 + if (mAudioEncoder && aQueuedMedia.GetType() == MediaSegment::AUDIO) { 1.58 + mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate, 1.59 + aTrackOffset, aTrackEvents, 1.60 + aQueuedMedia); 1.61 + 1.62 + } else if (mVideoEncoder && aQueuedMedia.GetType() == MediaSegment::VIDEO) { 1.63 + mVideoEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate, 1.64 + aTrackOffset, aTrackEvents, 1.65 + aQueuedMedia); 1.66 + } 1.67 +} 1.68 + 1.69 +void 1.70 +MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph) 1.71 +{ 1.72 + // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event. 1.73 + LOG(PR_LOG_DEBUG, ("NotifyRemoved in [MediaEncoder].")); 1.74 + if (mAudioEncoder) { 1.75 + mAudioEncoder->NotifyRemoved(aGraph); 1.76 + } 1.77 + if (mVideoEncoder) { 1.78 + mVideoEncoder->NotifyRemoved(aGraph); 1.79 + } 1.80 + 1.81 +} 1.82 + 1.83 +/* static */ 1.84 +already_AddRefed<MediaEncoder> 1.85 +MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes) 1.86 +{ 1.87 +#ifdef PR_LOGGING 1.88 + if (!gMediaEncoderLog) { 1.89 + gMediaEncoderLog = PR_NewLogModule("MediaEncoder"); 1.90 + } 1.91 +#endif 1.92 + nsAutoPtr<ContainerWriter> writer; 1.93 + nsAutoPtr<AudioTrackEncoder> audioEncoder; 1.94 + nsAutoPtr<VideoTrackEncoder> videoEncoder; 1.95 + nsRefPtr<MediaEncoder> encoder; 1.96 + nsString mimeType; 1.97 + if (!aTrackTypes) { 1.98 + LOG(PR_LOG_ERROR, ("NO TrackTypes!!!")); 1.99 + return nullptr; 1.100 + } 1.101 +#ifdef MOZ_WEBM_ENCODER 1.102 + else if (MediaEncoder::IsWebMEncoderEnabled() && 1.103 + (aMIMEType.EqualsLiteral(VIDEO_WEBM) || 1.104 + (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) { 1.105 + if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) { 1.106 + audioEncoder = new VorbisTrackEncoder(); 1.107 + NS_ENSURE_TRUE(audioEncoder, nullptr); 1.108 + } 1.109 + videoEncoder = new VP8TrackEncoder(); 1.110 + writer = new WebMWriter(aTrackTypes); 1.111 + NS_ENSURE_TRUE(writer, nullptr); 1.112 + NS_ENSURE_TRUE(videoEncoder, nullptr); 1.113 + mimeType = NS_LITERAL_STRING(VIDEO_WEBM); 1.114 + } 1.115 +#endif //MOZ_WEBM_ENCODER 1.116 +#ifdef MOZ_OMX_ENCODER 1.117 + else if (MediaEncoder::IsOMXEncoderEnabled() && 1.118 + (aMIMEType.EqualsLiteral(VIDEO_MP4) || 1.119 + (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) { 1.120 + if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK) { 1.121 + audioEncoder = new OmxAACAudioTrackEncoder(); 1.122 + NS_ENSURE_TRUE(audioEncoder, nullptr); 1.123 + } 1.124 + videoEncoder = new OmxVideoTrackEncoder(); 1.125 + writer = new ISOMediaWriter(aTrackTypes); 1.126 + NS_ENSURE_TRUE(writer, nullptr); 1.127 + NS_ENSURE_TRUE(videoEncoder, nullptr); 1.128 + mimeType = NS_LITERAL_STRING(VIDEO_MP4); 1.129 + } else if (MediaEncoder::IsOMXEncoderEnabled() && 1.130 + (aMIMEType.EqualsLiteral(AUDIO_3GPP))) { 1.131 + audioEncoder = new OmxAMRAudioTrackEncoder(); 1.132 + NS_ENSURE_TRUE(audioEncoder, nullptr); 1.133 + 1.134 + writer = new ISOMediaWriter(aTrackTypes, ISOMediaWriter::TYPE_FRAG_3GP); 1.135 + NS_ENSURE_TRUE(writer, nullptr); 1.136 + mimeType = NS_LITERAL_STRING(AUDIO_3GPP); 1.137 + } 1.138 +#endif // MOZ_OMX_ENCODER 1.139 + else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() && 1.140 + (aMIMEType.EqualsLiteral(AUDIO_OGG) || 1.141 + (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK))) { 1.142 + writer = new OggWriter(); 1.143 + audioEncoder = new OpusTrackEncoder(); 1.144 + NS_ENSURE_TRUE(writer, nullptr); 1.145 + NS_ENSURE_TRUE(audioEncoder, nullptr); 1.146 + mimeType = NS_LITERAL_STRING(AUDIO_OGG); 1.147 + } 1.148 + else { 1.149 + LOG(PR_LOG_ERROR, ("Can not find any encoder to record this media stream")); 1.150 + return nullptr; 1.151 + } 1.152 + LOG(PR_LOG_DEBUG, ("Create encoder result:a[%d] v[%d] w[%d] mimeType = %s.", 1.153 + audioEncoder != nullptr, videoEncoder != nullptr, 1.154 + writer != nullptr, mimeType.get())); 1.155 + encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(), 1.156 + videoEncoder.forget(), mimeType); 1.157 + return encoder.forget(); 1.158 +} 1.159 + 1.160 +/** 1.161 + * GetEncodedData() runs as a state machine, starting with mState set to 1.162 + * GET_METADDATA, the procedure should be as follow: 1.163 + * 1.164 + * While non-stop 1.165 + * If mState is GET_METADDATA 1.166 + * Get the meta data from audio/video encoder 1.167 + * If a meta data is generated 1.168 + * Get meta data from audio/video encoder 1.169 + * Set mState to ENCODE_TRACK 1.170 + * Return the final container data 1.171 + * 1.172 + * If mState is ENCODE_TRACK 1.173 + * Get encoded track data from audio/video encoder 1.174 + * If a packet of track data is generated 1.175 + * Insert encoded track data into the container stream of writer 1.176 + * If the final container data is copied to aOutput 1.177 + * Return the copy of final container data 1.178 + * If this is the last packet of input stream 1.179 + * Set mState to ENCODE_DONE 1.180 + * 1.181 + * If mState is ENCODE_DONE or ENCODE_ERROR 1.182 + * Stop the loop 1.183 + */ 1.184 +void 1.185 +MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs, 1.186 + nsAString& aMIMEType) 1.187 +{ 1.188 + MOZ_ASSERT(!NS_IsMainThread()); 1.189 + 1.190 + aMIMEType = mMIMEType; 1.191 + 1.192 + bool reloop = true; 1.193 + while (reloop) { 1.194 + switch (mState) { 1.195 + case ENCODE_METADDATA: { 1.196 + LOG(PR_LOG_DEBUG, ("ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp())); 1.197 + nsresult rv = CopyMetadataToMuxer(mAudioEncoder.get()); 1.198 + if (NS_FAILED(rv)) { 1.199 + LOG(PR_LOG_ERROR, ("Error! Fail to Set Audio Metadata")); 1.200 + break; 1.201 + } 1.202 + rv = CopyMetadataToMuxer(mVideoEncoder.get()); 1.203 + if (NS_FAILED(rv)) { 1.204 + LOG(PR_LOG_ERROR, ("Error! Fail to Set Video Metadata")); 1.205 + break; 1.206 + } 1.207 + 1.208 + rv = mWriter->GetContainerData(aOutputBufs, 1.209 + ContainerWriter::GET_HEADER); 1.210 + if (NS_FAILED(rv)) { 1.211 + LOG(PR_LOG_ERROR,("Error! writer fail to generate header!")); 1.212 + mState = ENCODE_ERROR; 1.213 + break; 1.214 + } 1.215 + LOG(PR_LOG_DEBUG, ("Finish ENCODE_METADDATA TimeStamp = %f", GetEncodeTimeStamp())); 1.216 + mState = ENCODE_TRACK; 1.217 + break; 1.218 + } 1.219 + 1.220 + case ENCODE_TRACK: { 1.221 + LOG(PR_LOG_DEBUG, ("ENCODE_TRACK TimeStamp = %f", GetEncodeTimeStamp())); 1.222 + EncodedFrameContainer encodedData; 1.223 + nsresult rv = NS_OK; 1.224 + rv = WriteEncodedDataToMuxer(mAudioEncoder.get()); 1.225 + if (NS_FAILED(rv)) { 1.226 + LOG(PR_LOG_ERROR, ("Error! Fail to write audio encoder data to muxer")); 1.227 + break; 1.228 + } 1.229 + LOG(PR_LOG_DEBUG, ("Audio encoded TimeStamp = %f", GetEncodeTimeStamp())); 1.230 + rv = WriteEncodedDataToMuxer(mVideoEncoder.get()); 1.231 + if (NS_FAILED(rv)) { 1.232 + LOG(PR_LOG_ERROR, ("Fail to write video encoder data to muxer")); 1.233 + break; 1.234 + } 1.235 + LOG(PR_LOG_DEBUG, ("Video encoded TimeStamp = %f", GetEncodeTimeStamp())); 1.236 + // In audio only or video only case, let unavailable track's flag to be true. 1.237 + bool isAudioCompleted = (mAudioEncoder && mAudioEncoder->IsEncodingComplete()) || !mAudioEncoder; 1.238 + bool isVideoCompleted = (mVideoEncoder && mVideoEncoder->IsEncodingComplete()) || !mVideoEncoder; 1.239 + rv = mWriter->GetContainerData(aOutputBufs, 1.240 + isAudioCompleted && isVideoCompleted ? 1.241 + ContainerWriter::FLUSH_NEEDED : 0); 1.242 + if (NS_SUCCEEDED(rv)) { 1.243 + // Successfully get the copy of final container data from writer. 1.244 + reloop = false; 1.245 + } 1.246 + mState = (mWriter->IsWritingComplete()) ? ENCODE_DONE : ENCODE_TRACK; 1.247 + LOG(PR_LOG_DEBUG, ("END ENCODE_TRACK TimeStamp = %f " 1.248 + "mState = %d aComplete %d vComplete %d", 1.249 + GetEncodeTimeStamp(), mState, isAudioCompleted, isVideoCompleted)); 1.250 + break; 1.251 + } 1.252 + 1.253 + case ENCODE_DONE: 1.254 + case ENCODE_ERROR: 1.255 + LOG(PR_LOG_DEBUG, ("MediaEncoder has been shutdown.")); 1.256 + mShutdown = true; 1.257 + reloop = false; 1.258 + break; 1.259 + default: 1.260 + MOZ_CRASH("Invalid encode state"); 1.261 + } 1.262 + } 1.263 +} 1.264 + 1.265 +nsresult 1.266 +MediaEncoder::WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder) 1.267 +{ 1.268 + if (aTrackEncoder == nullptr) { 1.269 + return NS_OK; 1.270 + } 1.271 + if (aTrackEncoder->IsEncodingComplete()) { 1.272 + return NS_OK; 1.273 + } 1.274 + EncodedFrameContainer encodedVideoData; 1.275 + nsresult rv = aTrackEncoder->GetEncodedTrack(encodedVideoData); 1.276 + if (NS_FAILED(rv)) { 1.277 + // Encoding might be canceled. 1.278 + LOG(PR_LOG_ERROR, ("Error! Fail to get encoded data from video encoder.")); 1.279 + mState = ENCODE_ERROR; 1.280 + return rv; 1.281 + } 1.282 + rv = mWriter->WriteEncodedTrack(encodedVideoData, 1.283 + aTrackEncoder->IsEncodingComplete() ? 1.284 + ContainerWriter::END_OF_STREAM : 0); 1.285 + if (NS_FAILED(rv)) { 1.286 + LOG(PR_LOG_ERROR, ("Error! Fail to write encoded video track to the media container.")); 1.287 + mState = ENCODE_ERROR; 1.288 + } 1.289 + return rv; 1.290 +} 1.291 + 1.292 +nsresult 1.293 +MediaEncoder::CopyMetadataToMuxer(TrackEncoder *aTrackEncoder) 1.294 +{ 1.295 + if (aTrackEncoder == nullptr) { 1.296 + return NS_OK; 1.297 + } 1.298 + nsRefPtr<TrackMetadataBase> meta = aTrackEncoder->GetMetadata(); 1.299 + if (meta == nullptr) { 1.300 + LOG(PR_LOG_ERROR, ("Error! metadata = null")); 1.301 + mState = ENCODE_ERROR; 1.302 + return NS_ERROR_ABORT; 1.303 + } 1.304 + 1.305 + nsresult rv = mWriter->SetMetadata(meta); 1.306 + if (NS_FAILED(rv)) { 1.307 + LOG(PR_LOG_ERROR, ("Error! SetMetadata fail")); 1.308 + mState = ENCODE_ERROR; 1.309 + } 1.310 + return rv; 1.311 +} 1.312 + 1.313 +#ifdef MOZ_WEBM_ENCODER 1.314 +bool 1.315 +MediaEncoder::IsWebMEncoderEnabled() 1.316 +{ 1.317 + return Preferences::GetBool("media.encoder.webm.enabled"); 1.318 +} 1.319 +#endif 1.320 + 1.321 +#ifdef MOZ_OMX_ENCODER 1.322 +bool 1.323 +MediaEncoder::IsOMXEncoderEnabled() 1.324 +{ 1.325 + return Preferences::GetBool("media.encoder.omx.enabled"); 1.326 +} 1.327 +#endif 1.328 + 1.329 +}