148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho/*
248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Copyright (C) 2016 The Android Open Source Project
348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Licensed under the Apache License, Version 2.0 (the "License");
548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * you may not use this file except in compliance with the License.
648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * You may obtain a copy of the License at
748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *      http://www.apache.org/licenses/LICENSE-2.0
948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho *
1048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Unless required by applicable law or agreed to in writing, software
1148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * distributed under the License is distributed on an "AS IS" BASIS,
1248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * See the License for the specific language governing permissions and
1448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * limitations under the License.
1548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho */
1648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
1748dadb49248271b01997862e1335912a4f2e189fYoungsang Chopackage com.android.usbtuner.exoplayer;
1848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
1948dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.MediaDataSource;
2048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.media.MediaExtractor;
2148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.ConditionVariable;
2248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.os.SystemClock;
2348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport android.util.Log;
2448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
2548dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.google.android.exoplayer.SampleHolder;
2648dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.exoplayer.cache.CacheManager;
2748dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.exoplayer.cache.RecordingSampleBuffer;
2848dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport com.android.usbtuner.tvinput.PlaybackCacheListener;
2948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
3048dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.io.IOException;
3148dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.ArrayList;
3248dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.List;
3348dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.Locale;
3448dadb49248271b01997862e1335912a4f2e189fYoungsang Choimport java.util.concurrent.atomic.AtomicLong;
3548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
3648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho/**
3748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho * Records live streams on the disk for DVR.
3848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho */
3948dadb49248271b01997862e1335912a4f2e189fYoungsang Chopublic class Recorder {
4048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final String TAG = "Recorder";
4148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
4248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    // Maximum bandwidth of 1080p channel is about 2.2MB/s. 2MB for a sample will suffice.
4348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final int SAMPLE_BUFFER_SIZE = 1024 * 1024 * 2;
4448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private static final AtomicLong ID_COUNTER = new AtomicLong(0);
4548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
4648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final MediaDataSource mDataSource;
4748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final MediaExtractor mMediaExtractor;
4848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final ExtractorThread mExtractorThread;
4948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private int mTrackCount;
5048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private List<android.media.MediaFormat> mMediaFormats;
5148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
5248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final CacheManager.SampleBuffer mSampleBuffer;
5348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
5448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean mReleased = false;
5548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private boolean mResultNotified = false;
5648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final long mId;
5748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
5848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private final RecordListener mRecordListener;
5948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
6048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    /**
6148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * Listeners for events which happens during the recording.
6248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     */
6348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public interface RecordListener {
6448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
6548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        /**
6648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho         * Notifies recording completion.
6748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho         *
6848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho         * @param success {@code true} when the recording succeeded, {@code false} otherwise
6948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho         */
7048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        void notifyRecordingFinished(boolean success);
7148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
7248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
7348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    /**
7448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * Create a recorder for a {@link android.media.MediaDataSource}.
7548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     *
7648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @param source {@link android.media.MediaDataSource} to record from
7748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @param cacheManager the manager for recording samples to physical storage
7848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @param cacheListener the {@link com.android.usbtuner.tvinput.PlaybackCacheListener}
7948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     *                      to notify cache storage status change
8048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @param recordListener RecordListener to notify events during the recording
8148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     */
8248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public Recorder(MediaDataSource source, CacheManager cacheManager,
8348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            PlaybackCacheListener cacheListener, RecordListener recordListener) {
8448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mDataSource = source;
8548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mMediaExtractor = new MediaExtractor();
8648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mExtractorThread = new ExtractorThread();
8748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mRecordListener = recordListener;
8848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
8948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSampleBuffer = new RecordingSampleBuffer(cacheManager, cacheListener, false,
9048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                RecordingSampleBuffer.CACHE_REASON_RECORDING);
9148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mId = ID_COUNTER.incrementAndGet();
9248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
9348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
9448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private class ExtractorThread extends Thread {
9548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private volatile boolean mQuitRequested = false;
9648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
9748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public ExtractorThread() {
9848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            super("ExtractorThread");
9948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
10048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
10148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        @Override
10248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public void run() {
10348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            SampleHolder sample = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL);
10448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.ensureSpaceForWrite(SAMPLE_BUFFER_SIZE);
10548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            ConditionVariable conditionVariable = new ConditionVariable();
10648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            while (!mQuitRequested) {
10748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                fetchSample(sample, conditionVariable);
10848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
10948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            cleanUp();
11048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
11148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
11248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        private void fetchSample(SampleHolder sample, ConditionVariable conditionVariable) {
11348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            int index = mMediaExtractor.getSampleTrackIndex();
11448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (index < 0) {
11548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Log.i(TAG, "EoS");
11648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mQuitRequested = true;
11748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mSampleBuffer.setEos();
11848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return;
11948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
12048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.data.clear();
12148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.size = mMediaExtractor.readSampleData(sample.data, 0);
12248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (sample.size < 0 || sample.size > SAMPLE_BUFFER_SIZE) {
12348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                // Should not happen
12448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                Log.e(TAG, "Invalid sample size: " + sample.size);
12548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mMediaExtractor.advance();
12648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                return;
12748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
12848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.data.position(sample.size);
12948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.timeUs = mMediaExtractor.getSampleTime();
13048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            sample.flags = mMediaExtractor.getSampleFlags();
13148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
13248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mMediaExtractor.advance();
13348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            try {
13448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                queueSample(index, sample, conditionVariable);
13548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            } catch (IOException e) {
13648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mQuitRequested = true;
13748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mSampleBuffer.setEos();
13848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
13948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
14048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
14148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        public void quit() {
14248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mQuitRequested = true;
14348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
14448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
14548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
14648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private void queueSample(int index, SampleHolder sample, ConditionVariable conditionVariable)
14748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            throws IOException {
14848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        long writeStartTimeNs = SystemClock.elapsedRealtimeNanos();
14948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSampleBuffer.writeSample(index, sample, conditionVariable);
15048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
15148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // Check if the storage has enough bandwidth for recording. Otherwise we disable it
15248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        // and notify the slowness.
15348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mSampleBuffer.isWriteSpeedSlow(sample.size,
15448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) {
15548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            Log.w(TAG, "Disk is too slow for trickplay. Disable trickplay.");
15648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            throw new IOException("Disk is too slow");
15748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
15848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
15948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
16048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    /**
16148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * Prepares a recording.
16248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     *
16348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @return {@code true} when preparation finished successfully, {@code false} otherwise
16448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * @throws IOException
16548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     */
16648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public boolean prepare() throws IOException {
16748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        synchronized (this) {
16848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mMediaExtractor.setDataSource(mDataSource);
16948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
17048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mTrackCount = mMediaExtractor.getTrackCount();
17148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            List<String> ids = new ArrayList<>();
17248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mMediaFormats = new ArrayList<>();
17348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            for (int i = 0; i < mTrackCount; i++) {
17448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                ids.add(String.format(Locale.ENGLISH, "%s_%x", Long.toHexString(mId), i));
17548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                android.media.MediaFormat format = mMediaExtractor.getTrackFormat(i);
17648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mMediaExtractor.selectTrack(i);
17748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mMediaFormats.add(format);
17848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
17948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mSampleBuffer.init(ids, mMediaFormats);
18048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
18148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mExtractorThread.start();
18248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        return true;
18348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
18448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
18548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    /**
18648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     * Releases all the resources which were used in the recording.
18748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho     */
18848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    public void release() {
18948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        synchronized (this) {
19048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mReleased = true;
19148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
19248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (mExtractorThread.isAlive()) {
19348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mExtractorThread.quit();
19448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            // We don't join here to prevent hang --- MediaExtractor is released at the thread.
19548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        } else {
19648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            cleanUp();
19748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
19848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
19948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
20048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    private synchronized void cleanUp() {
20148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (!mReleased) {
20248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            if (!mResultNotified) {
20348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mRecordListener.notifyRecordingFinished(false);
20448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho                mResultNotified = true;
20548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            }
20648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            return;
20748dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
20848dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mSampleBuffer.release();
20948dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        if (!mResultNotified) {
21048dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mRecordListener.notifyRecordingFinished(true);
21148dadb49248271b01997862e1335912a4f2e189fYoungsang Cho            mResultNotified = true;
21248dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        }
21348dadb49248271b01997862e1335912a4f2e189fYoungsang Cho        mMediaExtractor.release();
21448dadb49248271b01997862e1335912a4f2e189fYoungsang Cho    }
21548dadb49248271b01997862e1335912a4f2e189fYoungsang Cho
21648dadb49248271b01997862e1335912a4f2e189fYoungsang Cho}
217