summaryrefslogtreecommitdiff
path: root/chromium/media/base/android/java/src/org/chromium/media/WebAudioMediaCodecBridge.java
blob: 1de7e42b8d274baabc6a3e73cf1de28638d21292 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.media;

import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import java.io.File;
import java.nio.ByteBuffer;

import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;

@JNINamespace("media")
class WebAudioMediaCodecBridge {
    private static final boolean DEBUG = true;
    static final String LOG_TAG = "WebAudioMediaCodec";
    // TODO(rtoy): What is the correct timeout value for reading
    // from a file in memory?
    static final long TIMEOUT_MICROSECONDS = 500;
    @CalledByNative
    private static String CreateTempFile(Context ctx) throws java.io.IOException {
        File outputDirectory = ctx.getCacheDir();
        File outputFile = File.createTempFile("webaudio", ".dat", outputDirectory);
        return outputFile.getAbsolutePath();
    }

    @CalledByNative
    private static boolean decodeAudioFile(Context ctx,
                                           int nativeMediaCodecBridge,
                                           int inputFD,
                                           long dataSize) {

        if (dataSize < 0 || dataSize > 0x7fffffff)
            return false;

        MediaExtractor extractor = new MediaExtractor();

        ParcelFileDescriptor encodedFD;
        encodedFD = ParcelFileDescriptor.adoptFd(inputFD);
        try {
            extractor.setDataSource(encodedFD.getFileDescriptor(), 0, dataSize);
        } catch (Exception e) {
            e.printStackTrace();
            encodedFD.detachFd();
            return false;
        }

        if (extractor.getTrackCount() <= 0) {
            encodedFD.detachFd();
            return false;
        }

        MediaFormat format = extractor.getTrackFormat(0);

        // Number of channels specified in the file
        int inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);

        // Number of channels the decoder will provide. (Not
        // necessarily the same as inputChannelCount.  See
        // crbug.com/266006.)
        int outputChannelCount = inputChannelCount;

        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
        String mime = format.getString(MediaFormat.KEY_MIME);

        long durationMicroseconds = 0;
        if (format.containsKey(MediaFormat.KEY_DURATION)) {
            try {
                durationMicroseconds = format.getLong(MediaFormat.KEY_DURATION);
            } catch (Exception e) {
                Log.d(LOG_TAG, "Cannot get duration");
            }
        }

        if (DEBUG) {
            Log.d(LOG_TAG, "Tracks: " + extractor.getTrackCount()
                  + " Rate: " + sampleRate
                  + " Channels: " + inputChannelCount
                  + " Mime: " + mime
                  + " Duration: " + durationMicroseconds + " microsec");
        }

        nativeInitializeDestination(nativeMediaCodecBridge,
                                    inputChannelCount,
                                    sampleRate,
                                    durationMicroseconds);

        // Create decoder
        MediaCodec codec = MediaCodec.createDecoderByType(mime);
        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
        codec.start();

        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();

        // A track must be selected and will be used to read samples.
        extractor.selectTrack(0);

        boolean sawInputEOS = false;
        boolean sawOutputEOS = false;

        // Keep processing until the output is done.
        while (!sawOutputEOS) {
            if (!sawInputEOS) {
                // Input side
                int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECONDS);

                if (inputBufIndex >= 0) {
                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
                    int sampleSize = extractor.readSampleData(dstBuf, 0);
                    long presentationTimeMicroSec = 0;

                    if (sampleSize < 0) {
                        sawInputEOS = true;
                        sampleSize = 0;
                    } else {
                        presentationTimeMicroSec = extractor.getSampleTime();
                    }

                    codec.queueInputBuffer(inputBufIndex,
                                           0, /* offset */
                                           sampleSize,
                                           presentationTimeMicroSec,
                                           sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

                    if (!sawInputEOS) {
                        extractor.advance();
                    }
                }
            }

            // Output side
            MediaCodec.BufferInfo info = new BufferInfo();
            final int outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSECONDS);

            if (outputBufIndex >= 0) {
                ByteBuffer buf = codecOutputBuffers[outputBufIndex];

                if (info.size > 0) {
                    nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
                                         inputChannelCount, outputChannelCount);
                }

                buf.clear();
                codec.releaseOutputBuffer(outputBufIndex, false /* render */);

                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    sawOutputEOS = true;
                }
            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                codecOutputBuffers = codec.getOutputBuffers();
            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                MediaFormat newFormat = codec.getOutputFormat();
                outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                Log.d(LOG_TAG, "output format changed to " + newFormat);
            }
        }

        encodedFD.detachFd();

        codec.stop();
        codec.release();
        codec = null;

        return true;
    }

    private static native void nativeOnChunkDecoded(
        int nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size,
        int inputChannelCount, int outputChannelCount);

    private static native void nativeInitializeDestination(
        int nativeWebAudioMediaCodecBridge,
        int inputChannelCount,
        int sampleRate,
        long durationMicroseconds);
}