• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.usbtuner.exoplayer.ac3;
18 
19 import android.os.Handler;
20 import android.os.SystemClock;
21 import android.util.Log;
22 
23 import com.google.android.exoplayer.CodecCounters;
24 import com.google.android.exoplayer.ExoPlaybackException;
25 import com.google.android.exoplayer.MediaClock;
26 import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
27 import com.google.android.exoplayer.MediaFormat;
28 import com.google.android.exoplayer.MediaFormatHolder;
29 import com.google.android.exoplayer.MediaFormatUtil;
30 import com.google.android.exoplayer.SampleHolder;
31 import com.google.android.exoplayer.SampleSource;
32 import com.google.android.exoplayer.TrackRenderer;
33 import com.google.android.exoplayer.audio.AudioTrack;
34 import com.google.android.exoplayer.util.Assertions;
35 import com.google.android.exoplayer.util.MimeTypes;
36 import com.android.usbtuner.tvinput.UsbTunerDebug;
37 
38 import java.io.IOException;
39 import java.nio.ByteBuffer;
40 
41 /**
42  * Decodes and renders AC3 audio.
43  */
44 public class Ac3TrackRenderer extends TrackRenderer implements Ac3Decoder.DecodeListener,
45         MediaClock {
46     public static final int MSG_SET_VOLUME = MediaCodecAudioTrackRenderer.MSG_SET_VOLUME;
47     public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1;
48 
49     // ATSC/53 allows sample rate to be only 48Khz.
50     // One AC3 sample has 1536 frames, and its duration is 32ms.
51     public static final long AC3_SAMPLE_DURATION_US = 32000;
52 
53     private static final String TAG = "Ac3TrackRenderer";
54     private static final boolean DEBUG = false;
55 
56     /**
57      * Interface definition for a callback to be notified of
58      * {@link com.google.android.exoplayer.audio.AudioTrack} error.
59      */
60     public interface EventListener {
onAudioTrackInitializationError(AudioTrack.InitializationException e)61         void onAudioTrackInitializationError(AudioTrack.InitializationException e);
onAudioTrackWriteError(AudioTrack.WriteException e)62         void onAudioTrackWriteError(AudioTrack.WriteException e);
63     }
64 
65     private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2;
66     private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024;
67     private static final int MONITOR_DURATION_MS = 1000;
68     private static final int AC3_HEADER_BITRATE_OFFSET = 4;
69 
70     // Keep this as static in order to prevent new framework AudioTrack creation
71     // while old AudioTrack is being released.
72     private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper();
73     private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000;
74 
75     // Ignore AudioTrack backward movement if duration of movement is below the threshold.
76     private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000;
77 
78     // AudioTrack position cannot go ahead beyond this limit.
79     private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000;
80 
81     // Since MediaCodec processing and AudioTrack playing add delay,
82     // PTS interpolated time should be delayed reasonably when AudioTrack is not used.
83     private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000;
84 
85     private final CodecCounters mCodecCounters;
86     private final SampleSource.SampleSourceReader mSource;
87     private final SampleHolder mSampleHolder;
88     private final MediaFormatHolder mFormatHolder;
89     private final EventListener mEventListener;
90     private final Handler mEventHandler;
91     private final boolean mIsSoftware;
92     private final AudioTrackMonitor mMonitor;
93     private final AudioClock mAudioClock;
94 
95     private MediaFormat mFormat;
96     private Ac3Decoder mDecoder;
97     private ByteBuffer mOutputBuffer;
98     private boolean mOutputReady;
99     private int mTrackIndex;
100     private boolean mSourceStateReady;
101     private boolean mInputStreamEnded;
102     private boolean mOutputStreamEnded;
103     private long mEndOfStreamMs;
104     private long mCurrentPositionUs;
105     private int mPresentationCount;
106     private long mPresentationTimeUs;
107     private long mInterpolatedTimeUs;
108     private long mPreviousPositionUs;
109 
Ac3TrackRenderer(SampleSource source, Handler eventHandler, EventListener listener, boolean isSoftware)110     public Ac3TrackRenderer(SampleSource source, Handler eventHandler,
111             EventListener listener, boolean isSoftware) {
112         mSource = source.register();
113         mEventHandler = eventHandler;
114         mEventListener = listener;
115         mDecoder = Ac3Decoder.createAc3Decoder(isSoftware);
116         mIsSoftware = isSoftware;
117         mTrackIndex = -1;
118         mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
119         mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
120         mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE);
121         mFormatHolder = new MediaFormatHolder();
122         AUDIO_TRACK.restart();
123         mCodecCounters = new CodecCounters();
124         mMonitor = new AudioTrackMonitor();
125         mAudioClock = new AudioClock();
126     }
127 
128     @Override
getMediaClock()129     protected MediaClock getMediaClock() {
130         return this;
131     }
132 
handlesMimeType(String mimeType)133     private static boolean handlesMimeType(String mimeType) {
134         return mimeType.equals(MimeTypes.AUDIO_AC3) || mimeType.equals(MimeTypes.AUDIO_E_AC3);
135     }
136 
137     @Override
doPrepare(long positionUs)138     protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
139         boolean sourcePrepared = mSource.prepare(positionUs);
140         if (!sourcePrepared) {
141             return false;
142         }
143         for (int i = 0; i < mSource.getTrackCount(); i++) {
144             if (handlesMimeType(mSource.getFormat(i).mimeType)) {
145                 mTrackIndex = i;
146                 return true;
147             }
148         }
149 
150         // TODO: Check this case. Source does not have the proper mime type.
151         return true;
152     }
153 
154     @Override
getTrackCount()155     protected int getTrackCount() {
156         return mTrackIndex < 0 ? 0 : 1;
157     }
158 
159     @Override
getFormat(int track)160     protected MediaFormat getFormat(int track) {
161         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
162         return mSource.getFormat(mTrackIndex);
163     }
164 
165     @Override
onEnabled(int track, long positionUs, boolean joining)166     protected void onEnabled(int track, long positionUs, boolean joining) {
167         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
168         mSource.enable(mTrackIndex, positionUs);
169         mDecoder.startDecoder(this);
170         seekToInternal(positionUs);
171     }
172 
173     @Override
onDisabled()174     protected void onDisabled() {
175         AUDIO_TRACK.resetSessionId();
176         clearDecodeState();
177         mFormat = null;
178         mSource.disable(mTrackIndex);
179     }
180 
181     @Override
onReleased()182     protected void onReleased() {
183         AUDIO_TRACK.release();
184         mSource.release();
185     }
186 
187     @Override
isEnded()188     protected boolean isEnded() {
189         return mOutputStreamEnded && AUDIO_TRACK.isEnded();
190     }
191 
192     @Override
isReady()193     protected boolean isReady() {
194         return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady));
195     }
196 
seekToInternal(long positionUs)197     private void seekToInternal(long positionUs) {
198         mMonitor.reset(MONITOR_DURATION_MS);
199         mSourceStateReady = false;
200         mInputStreamEnded = false;
201         mOutputStreamEnded = false;
202         mPresentationTimeUs = positionUs;
203         mPresentationCount = 0;
204         mPreviousPositionUs = 0;
205         mCurrentPositionUs = Long.MIN_VALUE;
206         mInterpolatedTimeUs = Long.MIN_VALUE;
207         mAudioClock.setPositionUs(positionUs);
208     }
209 
210     @Override
seekTo(long positionUs)211     protected void seekTo(long positionUs) {
212         mSource.seekToUs(positionUs);
213         AUDIO_TRACK.reset();
214         // resetSessionId() will create a new framework AudioTrack instead of reusing old one.
215         if (!mIsSoftware) {
216             AUDIO_TRACK.resetSessionId();
217         }
218         seekToInternal(positionUs);
219     }
220 
221     @Override
onStarted()222     protected void onStarted() {
223         AUDIO_TRACK.play();
224         mAudioClock.start();
225     }
226 
227     @Override
onStopped()228     protected void onStopped() {
229         AUDIO_TRACK.pause();
230         mAudioClock.stop();
231     }
232 
233     @Override
maybeThrowError()234     protected void maybeThrowError() throws ExoPlaybackException {
235         try {
236             mSource.maybeThrowError();
237         } catch (IOException e) {
238             throw new ExoPlaybackException(e);
239         }
240     }
241 
242     @Override
doSomeWork(long positionUs, long elapsedRealtimeUs)243     protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
244         mMonitor.maybeLog();
245         try {
246             if (mEndOfStreamMs != 0) {
247                 // Ensure playback stops, after EoS was notified.
248                 // Sometimes MediaCodecTrackRenderer does not fetch EoS timely
249                 // after EoS was notified here long before.
250                 long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
251                 if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS) {
252                     throw new ExoPlaybackException("Much time has elapsed after EoS");
253                 }
254             }
255             boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs);
256             if (mSourceStateReady != continueBuffering) {
257                 mSourceStateReady = continueBuffering;
258                 if (DEBUG) {
259                     Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady));
260                 }
261             }
262             if (mFormat == null) {
263                 readFormat();
264                 return;
265             }
266 
267             // Process only one sample at a time for doSomeWork()
268             if (processOutput()) {
269                 if (!mOutputReady) {
270                     while (feedInputBuffer()) {
271                         if (mOutputReady) break;
272                     }
273                 }
274             }
275             mCodecCounters.ensureUpdated();
276         } catch (IOException e) {
277             throw new ExoPlaybackException(e);
278         }
279     }
280 
ensureAudioTrackInitialized()281     private void ensureAudioTrackInitialized() {
282         if (!AUDIO_TRACK.isInitialized()) {
283             try {
284                 if (DEBUG) {
285                     Log.d(TAG, "AudioTrack initialized");
286                 }
287                 AUDIO_TRACK.initialize();
288             } catch (AudioTrack.InitializationException e) {
289                 Log.e(TAG, "Error on AudioTrack initialization", e);
290                 notifyAudioTrackInitializationError(e);
291 
292                 // Do not throw exception here but just disabling audioTrack to keep playing
293                 // video without audio.
294                 AUDIO_TRACK.setStatus(false);
295             }
296             if (getState() == TrackRenderer.STATE_STARTED) {
297                 if (DEBUG) {
298                     Log.d(TAG, "AudioTrack played");
299                 }
300                 AUDIO_TRACK.play();
301             }
302         }
303     }
304 
clearDecodeState()305     private void clearDecodeState() {
306         mDecoder.startDecoder(this);
307         mOutputReady = false;
308         AUDIO_TRACK.reset();
309     }
310 
readFormat()311     private void readFormat() throws IOException, ExoPlaybackException {
312         int result = mSource.readData(mTrackIndex, mCurrentPositionUs,
313                 mFormatHolder, mSampleHolder);
314         if (result == SampleSource.FORMAT_READ) {
315             onInputFormatChanged(mFormatHolder);
316         }
317     }
318 
onInputFormatChanged(MediaFormatHolder formatHolder)319     private void onInputFormatChanged(MediaFormatHolder formatHolder)
320             throws ExoPlaybackException {
321         MediaFormat format = formatHolder.format;
322         if (mIsSoftware) {
323             mFormat = MediaFormatUtil.createAudioMediaFormat(MimeTypes.AUDIO_RAW, format.durationUs,
324                     format.channelCount, format.sampleRate);
325         } else {
326             mFormat = format;
327         }
328         if (DEBUG) {
329             Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
330         }
331         clearDecodeState();
332         AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16());
333     }
334 
feedInputBuffer()335     private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
336         if (mInputStreamEnded) {
337             return false;
338         }
339 
340         long discontinuity = mSource.readDiscontinuity(mTrackIndex);
341         if (discontinuity != SampleSource.NO_DISCONTINUITY) {
342             // TODO: handle input discontinuity for trickplay.
343             Log.i(TAG, "Read discontinuity happened");
344             AUDIO_TRACK.handleDiscontinuity();
345             mPresentationTimeUs = discontinuity;
346             mPresentationCount = 0;
347             clearDecodeState();
348             return false;
349         }
350 
351         mSampleHolder.data.clear();
352         mSampleHolder.size = 0;
353         int result = mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder,
354                 mSampleHolder);
355         switch (result) {
356             case SampleSource.NOTHING_READ: {
357                 return false;
358             }
359             case SampleSource.FORMAT_READ: {
360                 Log.i(TAG, "Format was read again");
361                 onInputFormatChanged(mFormatHolder);
362                 return true;
363             }
364             case SampleSource.END_OF_STREAM: {
365                 Log.i(TAG, "End of stream from SampleSource");
366                 mInputStreamEnded = true;
367                 return false;
368             }
369             default: {
370                 mSampleHolder.data.flip();
371                 mDecoder.decode(mSampleHolder.data, mSampleHolder.timeUs);
372                 return true;
373             }
374         }
375     }
376 
processOutput()377     private boolean processOutput() throws ExoPlaybackException {
378         if (mOutputStreamEnded) {
379             return false;
380         }
381         if (!mOutputReady) {
382             if (mInputStreamEnded) {
383                 mOutputStreamEnded = true;
384                 mEndOfStreamMs = SystemClock.elapsedRealtime();
385                 return false;
386             }
387             return true;
388         }
389 
390         ensureAudioTrackInitialized();
391         int handleBufferResult;
392         try {
393             // To reduce discontinuity, interpolate presentation time.
394             mInterpolatedTimeUs = mPresentationTimeUs
395                     + mPresentationCount * AC3_SAMPLE_DURATION_US;
396             handleBufferResult = AUDIO_TRACK.handleBuffer(mOutputBuffer,
397                     0, mOutputBuffer.limit(), mInterpolatedTimeUs);
398         } catch (AudioTrack.WriteException e) {
399             notifyAudioTrackWriteError(e);
400             throw new ExoPlaybackException(e);
401         }
402 
403         if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
404             Log.i(TAG, "Play discontinuity happened");
405             mCurrentPositionUs = Long.MIN_VALUE;
406         }
407         if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
408             mCodecCounters.renderedOutputBufferCount++;
409             mOutputReady = false;
410             return true;
411         }
412         return false;
413     }
414 
415     @Override
getDurationUs()416     protected long getDurationUs() {
417         return mSource.getFormat(mTrackIndex).durationUs;
418     }
419 
420     @Override
getBufferedPositionUs()421     protected long getBufferedPositionUs() {
422         long pos = mSource.getBufferedPositionUs();
423         return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US
424                 ? pos : Math.max(pos, getPositionUs());
425     }
426 
427     @Override
getPositionUs()428     public long getPositionUs() {
429         if (!AUDIO_TRACK.isInitialized()) {
430             return mAudioClock.getPositionUs();
431         } if (!AUDIO_TRACK.isEnabled()) {
432             if (mInterpolatedTimeUs > 0) {
433                 return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US;
434             }
435             return mPresentationTimeUs;
436         }
437         long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded());
438         if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) {
439             mPreviousPositionUs = 0L;
440             if (DEBUG) {
441                 long oldPositionUs = Math.max(mCurrentPositionUs, 0);
442                 long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
443                 Log.d(TAG, "Audio position is not set, diff in us: "
444                         + String.valueOf(currentPositionUs - oldPositionUs));
445             }
446             mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
447         } else {
448             if (!mIsSoftware && mPreviousPositionUs >
449                     audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
450                 Log.e(TAG, "audio_position BACK JUMP: "
451                         + (mPreviousPositionUs - audioTrackCurrentPositionUs));
452                 mCurrentPositionUs = audioTrackCurrentPositionUs;
453             } else {
454                 mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs);
455             }
456             mPreviousPositionUs = audioTrackCurrentPositionUs;
457         }
458         long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US;
459         if (mCurrentPositionUs > upperBound) {
460             mCurrentPositionUs = upperBound;
461         }
462         return mCurrentPositionUs;
463     }
464 
465     @Override
decodeDone(ByteBuffer outputBuffer, long presentationTimeUs)466     public void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) {
467         if (outputBuffer == null || mOutputBuffer == null) {
468             return;
469         }
470         if (presentationTimeUs < 0) {
471             Log.e(TAG, "decodeDone - invalid presentationTimeUs");
472             return;
473         }
474 
475         if (UsbTunerDebug.ENABLED) {
476             UsbTunerDebug.setAudioPtsUs(presentationTimeUs);
477         }
478 
479         mOutputBuffer.clear();
480         Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit());
481 
482         mOutputBuffer.put(outputBuffer);
483         mMonitor.addPts(presentationTimeUs, mOutputBuffer.position(),
484                 mOutputBuffer.get(AC3_HEADER_BITRATE_OFFSET));
485         if (presentationTimeUs == mPresentationTimeUs) {
486             mPresentationCount++;
487         } else {
488             mPresentationCount = 0;
489             mPresentationTimeUs = presentationTimeUs;
490         }
491         mOutputBuffer.flip();
492         mOutputReady = true;
493     }
494 
notifyAudioTrackInitializationError(final AudioTrack.InitializationException e)495     private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
496         if (mEventHandler == null || mEventListener == null) {
497             return;
498         }
499         mEventHandler.post(new Runnable() {
500             @Override
501             public void run() {
502                 mEventListener.onAudioTrackInitializationError(e);
503             }
504         });
505     }
506 
notifyAudioTrackWriteError(final AudioTrack.WriteException e)507     private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
508         if (mEventHandler == null || mEventListener == null) {
509             return;
510         }
511         mEventHandler.post(new Runnable() {
512             @Override
513             public void run() {
514                 mEventListener.onAudioTrackWriteError(e);
515             }
516         });
517     }
518 
519     @Override
handleMessage(int messageType, Object message)520     public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
521         switch (messageType) {
522             case MSG_SET_VOLUME:
523                 AUDIO_TRACK.setVolume((Float) message);
524                 break;
525             case MSG_SET_AUDIO_TRACK:
526                 AUDIO_TRACK.setStatus((Integer) message == 1);
527                 break;
528 
529             default:
530                 super.handleMessage(messageType, message);
531         }
532     }
533 }
534