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
17package com.android.tv.tuner.exoplayer.audio;
18
19import android.media.MediaFormat;
20
21import com.google.android.exoplayer.C;
22import com.google.android.exoplayer.audio.AudioTrack;
23
24import java.nio.ByteBuffer;
25
26/**
27 * {@link AudioTrack} wrapper class for trickplay operations including FF/RW.
28 * FF/RW trickplay operations do not need framework {@link AudioTrack}.
29 * This wrapper class will do nothing in disabled status for those operations.
30 */
31public class AudioTrackWrapper {
32    private static final int PCM16_FRAME_BYTES = 2;
33    private static final int AC3_FRAMES_IN_ONE_SAMPLE = 1536;
34    private static final int BUFFERED_SAMPLES_IN_AUDIOTRACK =
35            MpegTsDefaultAudioTrackRenderer.BUFFERED_SAMPLES_IN_AUDIOTRACK;
36    private final AudioTrack mAudioTrack = new AudioTrack();
37    private int mAudioSessionID;
38    private boolean mIsEnabled;
39
40    AudioTrackWrapper() {
41        mIsEnabled = true;
42    }
43
44    public void resetSessionId() {
45        mAudioSessionID = AudioTrack.SESSION_ID_NOT_SET;
46    }
47
48    public boolean isInitialized() {
49        return mIsEnabled && mAudioTrack.isInitialized();
50    }
51
52    public void restart() {
53        if (mAudioTrack.isInitialized()) {
54            mAudioTrack.release();
55        }
56        mIsEnabled = true;
57        resetSessionId();
58    }
59
60    public void release()  {
61        if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
62            mAudioTrack.release();
63        }
64    }
65
66    public void initialize() throws AudioTrack.InitializationException {
67        if (!mIsEnabled) {
68            return;
69        }
70        if (mAudioSessionID != AudioTrack.SESSION_ID_NOT_SET) {
71            mAudioTrack.initialize(mAudioSessionID);
72        } else {
73            mAudioSessionID = mAudioTrack.initialize();
74        }
75    }
76
77    public void reset() {
78        if (!mIsEnabled) {
79            return;
80        }
81        mAudioTrack.reset();
82    }
83
84    public boolean isEnded() {
85        return !mIsEnabled || !mAudioTrack.hasPendingData();
86    }
87
88    public boolean isReady() {
89        // In the case of not playing actual audio data, Audio track is always ready.
90        return !mIsEnabled || mAudioTrack.hasPendingData();
91    }
92
93    public void play() {
94        if (!mIsEnabled) {
95            return;
96        }
97        mAudioTrack.play();
98    }
99
100    public void pause() {
101        if (!mIsEnabled) {
102            return;
103        }
104        mAudioTrack.pause();
105    }
106
107    public void setVolume(float volume) {
108        if (!mIsEnabled) {
109            return;
110        }
111        mAudioTrack.setVolume(volume);
112    }
113
114    public void reconfigure(MediaFormat format, int audioBufferSize) {
115        if (!mIsEnabled || format == null) {
116            return;
117        }
118        String mimeType = format.getString(MediaFormat.KEY_MIME);
119        int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
120        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
121        int pcmEncoding;
122        try {
123            pcmEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING);
124        } catch (Exception e) {
125            pcmEncoding = C.ENCODING_PCM_16BIT;
126        }
127        // TODO: Handle non-AC3.
128        if (MediaFormat.MIMETYPE_AUDIO_AC3.equalsIgnoreCase(mimeType) && channelCount != 2) {
129            // Workarounds b/25955476.
130            // Since all devices and platforms does not support passthrough for non-stereo AC3,
131            // It is safe to fake non-stereo AC3 as AC3 stereo which is default passthrough mode.
132            // In other words, the channel count should be always 2.
133            channelCount = 2;
134        }
135        if (MediaFormat.MIMETYPE_AUDIO_RAW.equalsIgnoreCase(mimeType)) {
136            audioBufferSize =
137                    channelCount
138                            * PCM16_FRAME_BYTES
139                            * AC3_FRAMES_IN_ONE_SAMPLE
140                            * BUFFERED_SAMPLES_IN_AUDIOTRACK;
141        }
142        mAudioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, audioBufferSize);
143    }
144
145    public void handleDiscontinuity() {
146        if (!mIsEnabled) {
147            return;
148        }
149        mAudioTrack.handleDiscontinuity();
150    }
151
152    public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs)
153            throws AudioTrack.WriteException {
154        if (!mIsEnabled) {
155            return AudioTrack.RESULT_BUFFER_CONSUMED;
156        }
157        return mAudioTrack.handleBuffer(buffer, offset, size, presentationTimeUs);
158    }
159
160    public void setStatus(boolean enable) {
161        if (enable == mIsEnabled) {
162            return;
163        }
164        mAudioTrack.reset();
165        mIsEnabled = enable;
166    }
167
168    public boolean isEnabled() {
169        return mIsEnabled;
170    }
171
172    // This should be used only in case of being enabled.
173    public long getCurrentPositionUs(boolean isEnded) {
174        return mAudioTrack.getCurrentPositionUs(isEnded);
175    }
176}
177