|
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 |
|
6 #include "OmxTrackEncoder.h" |
|
7 #include "OMXCodecWrapper.h" |
|
8 #include "VideoUtils.h" |
|
9 #include "ISOTrackMetadata.h" |
|
10 |
|
11 #ifdef MOZ_WIDGET_GONK |
|
12 #include <android/log.h> |
|
13 #define OMX_LOG(args...) \ |
|
14 do { \ |
|
15 __android_log_print(ANDROID_LOG_INFO, "OmxTrackEncoder", ##args); \ |
|
16 } while (0) |
|
17 #else |
|
18 #define OMX_LOG(args, ...) |
|
19 #endif |
|
20 |
|
21 using namespace android; |
|
22 |
|
23 namespace mozilla { |
|
24 |
|
25 #define ENCODER_CONFIG_FRAME_RATE 30 // fps |
|
26 #define GET_ENCODED_VIDEO_FRAME_TIMEOUT 100000 // microseconds |
|
27 |
|
28 nsresult |
|
29 OmxVideoTrackEncoder::Init(int aWidth, int aHeight, int aDisplayWidth, |
|
30 int aDisplayHeight, TrackRate aTrackRate) |
|
31 { |
|
32 mFrameWidth = aWidth; |
|
33 mFrameHeight = aHeight; |
|
34 mTrackRate = aTrackRate; |
|
35 mDisplayWidth = aDisplayWidth; |
|
36 mDisplayHeight = aDisplayHeight; |
|
37 |
|
38 mEncoder = OMXCodecWrapper::CreateAVCEncoder(); |
|
39 NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); |
|
40 |
|
41 nsresult rv = mEncoder->Configure(mFrameWidth, mFrameHeight, |
|
42 ENCODER_CONFIG_FRAME_RATE); |
|
43 |
|
44 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
45 mInitialized = (rv == NS_OK); |
|
46 |
|
47 mReentrantMonitor.NotifyAll(); |
|
48 |
|
49 return rv; |
|
50 } |
|
51 |
|
52 already_AddRefed<TrackMetadataBase> |
|
53 OmxVideoTrackEncoder::GetMetadata() |
|
54 { |
|
55 { |
|
56 // Wait if mEncoder is not initialized nor is being canceled. |
|
57 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
58 while (!mCanceled && !mInitialized) { |
|
59 mReentrantMonitor.Wait(); |
|
60 } |
|
61 } |
|
62 |
|
63 if (mCanceled || mEncodingComplete) { |
|
64 return nullptr; |
|
65 } |
|
66 |
|
67 nsRefPtr<AVCTrackMetadata> meta = new AVCTrackMetadata(); |
|
68 meta->mWidth = mFrameWidth; |
|
69 meta->mHeight = mFrameHeight; |
|
70 meta->mDisplayWidth = mDisplayWidth; |
|
71 meta->mDisplayHeight = mDisplayHeight; |
|
72 meta->mFrameRate = ENCODER_CONFIG_FRAME_RATE; |
|
73 return meta.forget(); |
|
74 } |
|
75 |
|
76 nsresult |
|
77 OmxVideoTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) |
|
78 { |
|
79 VideoSegment segment; |
|
80 { |
|
81 // Move all the samples from mRawSegment to segment. We only hold the |
|
82 // monitor in this block. |
|
83 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
84 |
|
85 // Wait if mEncoder is not initialized nor is being canceled. |
|
86 while (!mCanceled && (!mInitialized || |
|
87 (mRawSegment.GetDuration() == 0 && !mEndOfStream))) { |
|
88 mReentrantMonitor.Wait(); |
|
89 } |
|
90 |
|
91 if (mCanceled || mEncodingComplete) { |
|
92 return NS_ERROR_FAILURE; |
|
93 } |
|
94 |
|
95 segment.AppendFrom(&mRawSegment); |
|
96 } |
|
97 |
|
98 // Start queuing raw frames to the input buffers of OMXCodecWrapper. |
|
99 VideoSegment::ChunkIterator iter(segment); |
|
100 while (!iter.IsEnded()) { |
|
101 VideoChunk chunk = *iter; |
|
102 |
|
103 // Send only the unique video frames to OMXCodecWrapper. |
|
104 if (mLastFrame != chunk.mFrame) { |
|
105 uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate; |
|
106 layers::Image* img = (chunk.IsNull() || chunk.mFrame.GetForceBlack()) ? |
|
107 nullptr : chunk.mFrame.GetImage(); |
|
108 mEncoder->Encode(img, mFrameWidth, mFrameHeight, totalDurationUs); |
|
109 } |
|
110 |
|
111 mLastFrame.TakeFrom(&chunk.mFrame); |
|
112 mTotalFrameDuration += chunk.GetDuration(); |
|
113 |
|
114 iter.Next(); |
|
115 } |
|
116 |
|
117 // Send the EOS signal to OMXCodecWrapper. |
|
118 if (mEndOfStream && iter.IsEnded() && !mEosSetInEncoder) { |
|
119 uint64_t totalDurationUs = mTotalFrameDuration * USECS_PER_S / mTrackRate; |
|
120 layers::Image* img = (!mLastFrame.GetImage() || mLastFrame.GetForceBlack()) |
|
121 ? nullptr : mLastFrame.GetImage(); |
|
122 nsresult result = mEncoder->Encode(img, mFrameWidth, mFrameHeight, |
|
123 totalDurationUs, |
|
124 OMXCodecWrapper::BUFFER_EOS); |
|
125 // Keep sending EOS signal until OMXVideoEncoder gets it. |
|
126 if (result == NS_OK) { |
|
127 mEosSetInEncoder = true; |
|
128 } |
|
129 } |
|
130 |
|
131 // Dequeue an encoded frame from the output buffers of OMXCodecWrapper. |
|
132 nsresult rv; |
|
133 nsTArray<uint8_t> buffer; |
|
134 int outFlags = 0; |
|
135 int64_t outTimeStampUs = 0; |
|
136 mEncoder->GetNextEncodedFrame(&buffer, &outTimeStampUs, &outFlags, |
|
137 GET_ENCODED_VIDEO_FRAME_TIMEOUT); |
|
138 if (!buffer.IsEmpty()) { |
|
139 nsRefPtr<EncodedFrame> videoData = new EncodedFrame(); |
|
140 if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) { |
|
141 videoData->SetFrameType(EncodedFrame::AVC_CSD); |
|
142 } else { |
|
143 videoData->SetFrameType((outFlags & OMXCodecWrapper::BUFFER_SYNC_FRAME) ? |
|
144 EncodedFrame::AVC_I_FRAME : EncodedFrame::AVC_P_FRAME); |
|
145 } |
|
146 rv = videoData->SwapInFrameData(buffer); |
|
147 NS_ENSURE_SUCCESS(rv, rv); |
|
148 videoData->SetTimeStamp(outTimeStampUs); |
|
149 aData.AppendEncodedFrame(videoData); |
|
150 } |
|
151 |
|
152 if (outFlags & OMXCodecWrapper::BUFFER_EOS) { |
|
153 mEncodingComplete = true; |
|
154 OMX_LOG("Done encoding video."); |
|
155 } |
|
156 |
|
157 return NS_OK; |
|
158 } |
|
159 |
|
160 nsresult |
|
161 OmxAudioTrackEncoder::AppendEncodedFrames(EncodedFrameContainer& aContainer) |
|
162 { |
|
163 nsTArray<uint8_t> frameData; |
|
164 int outFlags = 0; |
|
165 int64_t outTimeUs = -1; |
|
166 |
|
167 nsresult rv = mEncoder->GetNextEncodedFrame(&frameData, &outTimeUs, &outFlags, |
|
168 3000); // wait up to 3ms |
|
169 NS_ENSURE_SUCCESS(rv, rv); |
|
170 |
|
171 if (!frameData.IsEmpty()) { |
|
172 bool isCSD = false; |
|
173 if (outFlags & OMXCodecWrapper::BUFFER_CODEC_CONFIG) { // codec specific data |
|
174 isCSD = true; |
|
175 } else if (outFlags & OMXCodecWrapper::BUFFER_EOS) { // last frame |
|
176 mEncodingComplete = true; |
|
177 } |
|
178 |
|
179 nsRefPtr<EncodedFrame> audiodata = new EncodedFrame(); |
|
180 if (mEncoder->GetCodecType() == OMXCodecWrapper::AAC_ENC) { |
|
181 audiodata->SetFrameType(isCSD ? |
|
182 EncodedFrame::AAC_CSD : EncodedFrame::AAC_AUDIO_FRAME); |
|
183 } else if (mEncoder->GetCodecType() == OMXCodecWrapper::AMR_NB_ENC){ |
|
184 audiodata->SetFrameType(isCSD ? |
|
185 EncodedFrame::AMR_AUDIO_CSD : EncodedFrame::AMR_AUDIO_FRAME); |
|
186 } else { |
|
187 MOZ_ASSERT("audio codec not supported"); |
|
188 } |
|
189 audiodata->SetTimeStamp(outTimeUs); |
|
190 rv = audiodata->SwapInFrameData(frameData); |
|
191 NS_ENSURE_SUCCESS(rv, rv); |
|
192 aContainer.AppendEncodedFrame(audiodata); |
|
193 } |
|
194 |
|
195 return NS_OK; |
|
196 } |
|
197 |
|
198 nsresult |
|
199 OmxAudioTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) |
|
200 { |
|
201 AudioSegment segment; |
|
202 // Move all the samples from mRawSegment to segment. We only hold |
|
203 // the monitor in this block. |
|
204 { |
|
205 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
206 |
|
207 // Wait if mEncoder is not initialized nor canceled. |
|
208 while (!mInitialized && !mCanceled) { |
|
209 mReentrantMonitor.Wait(); |
|
210 } |
|
211 |
|
212 if (mCanceled || mEncodingComplete) { |
|
213 return NS_ERROR_FAILURE; |
|
214 } |
|
215 |
|
216 segment.AppendFrom(&mRawSegment); |
|
217 } |
|
218 |
|
219 nsresult rv; |
|
220 if (segment.GetDuration() == 0) { |
|
221 // Notify EOS at least once, even if segment is empty. |
|
222 if (mEndOfStream && !mEosSetInEncoder) { |
|
223 mEosSetInEncoder = true; |
|
224 rv = mEncoder->Encode(segment, OMXCodecWrapper::BUFFER_EOS); |
|
225 NS_ENSURE_SUCCESS(rv, rv); |
|
226 } |
|
227 // Nothing to encode but encoder could still have encoded data for earlier |
|
228 // input. |
|
229 return AppendEncodedFrames(aData); |
|
230 } |
|
231 |
|
232 // OMX encoder has limited input buffers only so we have to feed input and get |
|
233 // output more than once if there are too many samples pending in segment. |
|
234 while (segment.GetDuration() > 0) { |
|
235 rv = mEncoder->Encode(segment, |
|
236 mEndOfStream ? OMXCodecWrapper::BUFFER_EOS : 0); |
|
237 NS_ENSURE_SUCCESS(rv, rv); |
|
238 |
|
239 rv = AppendEncodedFrames(aData); |
|
240 NS_ENSURE_SUCCESS(rv, rv); |
|
241 } |
|
242 |
|
243 return NS_OK; |
|
244 } |
|
245 |
|
246 nsresult |
|
247 OmxAACAudioTrackEncoder::Init(int aChannels, int aSamplingRate) |
|
248 { |
|
249 mChannels = aChannels; |
|
250 mSamplingRate = aSamplingRate; |
|
251 |
|
252 mEncoder = OMXCodecWrapper::CreateAACEncoder(); |
|
253 NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); |
|
254 |
|
255 nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, mSamplingRate); |
|
256 |
|
257 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
258 mInitialized = (rv == NS_OK); |
|
259 |
|
260 mReentrantMonitor.NotifyAll(); |
|
261 |
|
262 return NS_OK; |
|
263 } |
|
264 |
|
265 already_AddRefed<TrackMetadataBase> |
|
266 OmxAACAudioTrackEncoder::GetMetadata() |
|
267 { |
|
268 { |
|
269 // Wait if mEncoder is not initialized nor is being canceled. |
|
270 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
271 while (!mCanceled && !mInitialized) { |
|
272 mReentrantMonitor.Wait(); |
|
273 } |
|
274 } |
|
275 |
|
276 if (mCanceled || mEncodingComplete) { |
|
277 return nullptr; |
|
278 } |
|
279 nsRefPtr<AACTrackMetadata> meta = new AACTrackMetadata(); |
|
280 meta->mChannels = mChannels; |
|
281 meta->mSampleRate = mSamplingRate; |
|
282 meta->mFrameSize = OMXCodecWrapper::kAACFrameSize; |
|
283 meta->mFrameDuration = OMXCodecWrapper::kAACFrameDuration; |
|
284 return meta.forget(); |
|
285 } |
|
286 |
|
287 nsresult |
|
288 OmxAMRAudioTrackEncoder::Init(int aChannels, int aSamplingRate) |
|
289 { |
|
290 mChannels = aChannels; |
|
291 mSamplingRate = aSamplingRate; |
|
292 |
|
293 mEncoder = OMXCodecWrapper::CreateAMRNBEncoder(); |
|
294 NS_ENSURE_TRUE(mEncoder, NS_ERROR_FAILURE); |
|
295 |
|
296 nsresult rv = mEncoder->Configure(mChannels, mSamplingRate, AMR_NB_SAMPLERATE); |
|
297 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
298 mInitialized = (rv == NS_OK); |
|
299 |
|
300 mReentrantMonitor.NotifyAll(); |
|
301 |
|
302 return NS_OK; |
|
303 } |
|
304 |
|
305 already_AddRefed<TrackMetadataBase> |
|
306 OmxAMRAudioTrackEncoder::GetMetadata() |
|
307 { |
|
308 { |
|
309 // Wait if mEncoder is not initialized nor is being canceled. |
|
310 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
311 while (!mCanceled && !mInitialized) { |
|
312 mReentrantMonitor.Wait(); |
|
313 } |
|
314 } |
|
315 |
|
316 if (mCanceled || mEncodingComplete) { |
|
317 return nullptr; |
|
318 } |
|
319 |
|
320 nsRefPtr<AMRTrackMetadata> meta = new AMRTrackMetadata(); |
|
321 return meta.forget(); |
|
322 } |
|
323 |
|
324 } |