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