165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/*
265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2015 The Android Open Source Project
365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License.
665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at
765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software
1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and
1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License.
1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.source;
1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport android.content.Context;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.util.Log;
216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport android.util.Pair;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
23d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.google.android.exoplayer.C;
24d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.google.android.exoplayer.upstream.DataSpec;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.SoftPreconditions;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.ChannelScanFileParser;
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.TunerHal;
28d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalkoimport com.android.tv.tuner.TunerPreferences;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.TunerChannel;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.tvinput.EventDetector;
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.tvinput.EventDetector.EventListener;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.io.IOException;
346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalkoimport java.util.ArrayList;
3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.List;
3665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.concurrent.atomic.AtomicLong;
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/**
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Provides MPEG-2 TS stream sources for channel playing from an underlying tuner device.
4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopublic class TunerTsStreamer implements TsStreamer {
4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final String TAG = "TunerTsStreamer";
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
4465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int MIN_READ_UNIT = 1500;
4565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int READ_BUFFER_SIZE = MIN_READ_UNIT * 10; // ~15KB
4665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int CIRCULAR_BUFFER_SIZE = MIN_READ_UNIT * 20000;  // ~ 30MB
476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int TS_PACKET_SIZE = 188;
4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int READ_TIMEOUT_MS = 5000; // 5 secs.
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static final int BUFFER_UNDERRUN_SLEEP_MS = 10;
516ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int READ_ERROR_STREAMING_ENDED = -1;
526ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private static final int READ_ERROR_BUFFER_OVERWRITTEN = -2;
5365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Object mCircularBufferMonitor = new Object();
5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final byte[] mCircularBuffer = new byte[CIRCULAR_BUFFER_SIZE];
5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private long mBytesFetched;
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final AtomicLong mLastReadPosition = new AtomicLong();
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private boolean mStreaming;
5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final TunerHal mTunerHal;
6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerChannel mChannel;
6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private Thread mStreamingThread;
6365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final EventDetector mEventDetector;
646ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private final List<Pair<EventListener, Boolean>> mEventListenerActions = new ArrayList<>();
6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
66d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private final TsStreamWriter mTsStreamWriter;
676ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private String mChannelNumber;
68d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
69d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public static class TunerDataSource extends TsDataSource {
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final TunerTsStreamer mTsStreamer;
7165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final AtomicLong mLastReadPosition = new AtomicLong(0);
7265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private long mStartBufferedPosition;
7365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
74d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        private TunerDataSource(TunerTsStreamer tsStreamer) {
7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTsStreamer = tsStreamer;
7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStartBufferedPosition = tsStreamer.getBufferedPosition();
7765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public long getBufferedPosition() {
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return mTsStreamer.getBufferedPosition() - mStartBufferedPosition;
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public long getLastReadPosition() {
8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return mLastReadPosition.get();
8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
8865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
8965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void shiftStartPosition(long offset) {
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            SoftPreconditions.checkState(mLastReadPosition.get() == 0);
9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            SoftPreconditions.checkArgument(0 <= offset && offset <= getBufferedPosition());
9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStartBufferedPosition += offset;
9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
96d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
97d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public long open(DataSpec dataSpec) throws IOException {
98d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            mLastReadPosition.set(0);
99d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return C.LENGTH_UNBOUNDED;
100d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
101d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
10265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void close() {
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
105d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
106d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        @Override
107d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        public int read(byte[] buffer, int offset, int readLength) throws IOException {
108d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            int ret = mTsStreamer.readAt(mStartBufferedPosition + mLastReadPosition.get(), buffer,
109d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    offset, readLength);
110d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (ret > 0) {
111d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mLastReadPosition.addAndGet(ret);
1126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            } else if (ret == READ_ERROR_BUFFER_OVERWRITTEN) {
1136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                long currentPosition = mStartBufferedPosition + mLastReadPosition.get();
1146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                long endPosition = mTsStreamer.getBufferedPosition();
1156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                long diff = ((endPosition - currentPosition + TS_PACKET_SIZE - 1) / TS_PACKET_SIZE)
1166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        * TS_PACKET_SIZE;
1176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                Log.w(TAG, "Demux position jump by overwritten buffer: " + diff);
1186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mStartBufferedPosition = currentPosition + diff;
1196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mLastReadPosition.set(0);
1206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                return 0;
121d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
122d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            return ret;
123d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Creates {@link TsStreamer} for playing or recording the specified channel.
12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param tunerHal the HAL for tuner device
12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param eventListener the listener for channel & program information
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
130d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener, Context context) {
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerHal = tunerHal;
1326ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        mEventDetector = new EventDetector(mTunerHal);
1336ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (eventListener != null) {
1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mEventDetector.registerListener(eventListener);
1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
136d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        mTsStreamWriter = context != null && TunerPreferences.getStoreTsStream(context) ?
137d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                new TsStreamWriter(context) : null;
138d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    }
139d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
140d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public TunerTsStreamer(TunerHal tunerHal, EventListener eventListener) {
141d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        this(tunerHal, eventListener, null);
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
14465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public boolean startStream(TunerChannel channel) {
1466ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mTunerHal.tune(channel.getFrequency(), channel.getModulation(),
1476ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                channel.getDisplayNumber(false))) {
14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (channel.hasVideo()) {
14965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mTunerHal.addPidFilter(channel.getVideoPid(),
15065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        TunerHal.FILTER_TYPE_VIDEO);
15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
152d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            boolean audioFilterSet = false;
153d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            for (Integer audioPid : channel.getAudioPids()) {
154d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (!audioFilterSet) {
155d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_AUDIO);
156d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    audioFilterSet = true;
157d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                } else {
158d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    // FILTER_TYPE_AUDIO overrides the previous filter for audio. We use
159d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    // FILTER_TYPE_OTHER from the secondary one to get the all audio tracks.
160d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    mTunerHal.addPidFilter(audioPid, TunerHal.FILTER_TYPE_OTHER);
161d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTunerHal.addPidFilter(channel.getPcrPid(),
16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    TunerHal.FILTER_TYPE_PCR);
16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mEventDetector != null) {
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mEventDetector.startDetecting(channel.getFrequency(), channel.getModulation(),
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        channel.getProgramNumber());
16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mChannel = channel;
1706ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mChannelNumber = channel.getDisplayNumber();
17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            synchronized (mCircularBufferMonitor) {
17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mStreaming) {
17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.w(TAG, "Streaming should be stopped before start streaming");
17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
17565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreaming = true;
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mBytesFetched = 0;
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mLastReadPosition.set(0L);
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
180d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            if (mTsStreamWriter != null) {
181d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mTsStreamWriter.setChannel(mChannel);
182d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                mTsStreamWriter.openFile();
183d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            }
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamingThread = new StreamingThread();
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamingThread.start();
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.i(TAG, "Streaming started");
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return true;
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return false;
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public boolean startStream(ChannelScanFileParser.ScanChannel channel) {
1946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mTunerHal.tune(channel.frequency, channel.modulation, null)) {
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mEventDetector.startDetecting(
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    channel.frequency, channel.modulation, EventDetector.ALL_PROGRAM_NUMBERS);
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            synchronized (mCircularBufferMonitor) {
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mStreaming) {
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    Log.w(TAG, "Streaming should be stopped before start streaming");
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return true;
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreaming = true;
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mBytesFetched = 0;
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mLastReadPosition.set(0L);
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamingThread = new StreamingThread();
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamingThread.start();
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.i(TAG, "Streaming started");
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return true;
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return false;
21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Blocks the current thread until the streaming thread stops. In rare cases when the tuner
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * device is overloaded this can take a while, but usually it returns pretty quickly.
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
21965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public void stopStream() {
22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mChannel = null;
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCircularBufferMonitor) {
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreaming = false;
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mCircularBufferMonitor.notifyAll();
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        try {
22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mStreamingThread != null) {
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreamingThread.join();
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        } catch (InterruptedException e) {
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Thread.currentThread().interrupt();
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
233d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        if (mTsStreamWriter != null) {
234d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            mTsStreamWriter.closeFile(true);
235d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko            mTsStreamWriter.setChannel(null);
236d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        }
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
23865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
23965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    @Override
240d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    public TsDataSource createDataSource() {
241d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko        return new TunerDataSource(this);
24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns incomplete channel lists which was scanned so far. Incomplete channel means
24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * the channel whose channel information is not complete or is not well-formed.
24765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return {@link List} of {@link TunerChannel}
24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public List<TunerChannel> getMalFormedChannels() {
25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mEventDetector.getMalFormedChannels();
25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
25465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the current {@link TunerHal} which provides MPEG-TS stream for TunerTsStreamer.
25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return {@link TunerHal}
25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public TunerHal getTunerHal() {
25865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mTunerHal;
25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the current tuned channel for TunerTsStreamer.
26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return {@link TunerChannel}
26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public TunerChannel getChannel() {
26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return mChannel;
26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the current buffered position from tuner.
27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return the current buffered position
27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public long getBufferedPosition() {
27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCircularBufferMonitor) {
27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return mBytesFetched;
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
27765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
2796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public String getStreamerInfo() {
2806ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        return "Channel: " + mChannelNumber + ", Streaming: " + mStreaming;
2816ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public void registerListener(EventListener listener) {
2846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mEventDetector != null && listener != null) {
2856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            synchronized (mEventListenerActions) {
2866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mEventListenerActions.add(new Pair<>(listener, true));
2876ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
2886ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
2896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2906ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
2916ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    public void unregisterListener(EventListener listener) {
2926ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        if (mEventDetector != null) {
2936ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            synchronized (mEventListenerActions) {
2946ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mEventListenerActions.add(new Pair(listener, false));
2956ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            }
2966ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
2976ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
2986ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
29965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private class StreamingThread extends Thread {
30065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        @Override
30165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        public void run() {
30265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Buffers for streaming data from the tuner and the internal buffer.
30365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            byte[] dataBuffer = new byte[READ_BUFFER_SIZE];
30465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
30565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            while (true) {
30665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                synchronized (mCircularBufferMonitor) {
30765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (!mStreaming) {
30865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        break;
30965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
31065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
31165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (mEventDetector != null) {
3136ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    synchronized (mEventListenerActions) {
3146ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        for (Pair listenerAction : mEventListenerActions) {
3156ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            EventListener listener = (EventListener) listenerAction.first;
3166ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            if ((boolean) listenerAction.second) {
3176ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                mEventDetector.registerListener(listener);
3186ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            } else {
3196ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                                mEventDetector.unregisterListener(listener);
3206ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                            }
3216ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        }
3226ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                        mEventListenerActions.clear();
3236ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    }
3246ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
3256ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
32665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                int bytesWritten = mTunerHal.readTsStream(dataBuffer, dataBuffer.length);
32765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (bytesWritten <= 0) {
32865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    try {
32965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        // When buffer is underrun, we sleep for short time to prevent
33065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        // unnecessary CPU draining.
33165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        sleep(BUFFER_UNDERRUN_SLEEP_MS);
33265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    } catch (InterruptedException e) {
33365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        Thread.currentThread().interrupt();
33465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
33565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    continue;
33665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
33765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
338d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                if (mTsStreamWriter != null) {
339d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                    mTsStreamWriter.writeToFile(dataBuffer, bytesWritten);
340d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                }
341d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko
34265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mEventDetector != null) {
34365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mEventDetector.feedTSStream(dataBuffer, 0, bytesWritten);
34465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
34565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                synchronized (mCircularBufferMonitor) {
34665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    int posInBuffer = (int) (mBytesFetched % CIRCULAR_BUFFER_SIZE);
34765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    int bytesToCopyInFirstPass = bytesWritten;
34865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (posInBuffer + bytesToCopyInFirstPass > mCircularBuffer.length) {
34965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        bytesToCopyInFirstPass = mCircularBuffer.length - posInBuffer;
35065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
35165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    System.arraycopy(dataBuffer, 0, mCircularBuffer, posInBuffer,
35265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                            bytesToCopyInFirstPass);
35365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    if (bytesToCopyInFirstPass < bytesWritten) {
35465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        System.arraycopy(dataBuffer, bytesToCopyInFirstPass, mCircularBuffer, 0,
35565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                                bytesWritten - bytesToCopyInFirstPass);
35665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
35765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mBytesFetched += bytesWritten;
35865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mCircularBufferMonitor.notifyAll();
35965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
36065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
36165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
36265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Log.i(TAG, "Streaming stopped");
36365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
36465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
36565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
36665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
36765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Reads data from internal buffer.
36865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param pos the position to read from
36965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param buffer to read
37065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param offset start position of the read buffer
37165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @param amount number of bytes to read
37265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return number of read bytes when successful, {@code -1} otherwise
37365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @throws IOException
37465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
37565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    public int readAt(long pos, byte[] buffer, int offset, int amount) throws IOException {
37665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        while (true) {
37765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            synchronized (mCircularBufferMonitor) {
3786ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (!mStreaming) {
3796ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return READ_ERROR_STREAMING_ENDED;
38065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
38165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mBytesFetched - CIRCULAR_BUFFER_SIZE > pos) {
3826ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    Log.w(TAG, "Demux is requesting the data which is already overwritten.");
3836ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    return READ_ERROR_BUFFER_OVERWRITTEN;
38465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
38565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mBytesFetched < pos + amount) {
38665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    try {
38765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        mCircularBufferMonitor.wait(READ_TIMEOUT_MS);
38865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    } catch (InterruptedException e) {
38965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                        Thread.currentThread().interrupt();
39065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    }
39165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // Try again to prevent starvation.
39265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    // Give chances to read from other threads.
39365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    continue;
39465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
39565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                int startPos = (int) (pos % CIRCULAR_BUFFER_SIZE);
39665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                int endPos = (int) ((pos + amount) % CIRCULAR_BUFFER_SIZE);
39765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                int firstLength = (startPos > endPos ? CIRCULAR_BUFFER_SIZE : endPos) - startPos;
39865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                System.arraycopy(mCircularBuffer, startPos, buffer, offset, firstLength);
39965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (firstLength < amount) {
40065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    System.arraycopy(mCircularBuffer, 0, buffer, offset + firstLength,
40165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                            amount - firstLength);
40265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
40365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mCircularBufferMonitor.notifyAll();
40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return amount;
40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
40865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}
409