为.ts段创建MP4容器

时间:2014-12-09 23:02:58

标签: android video hls

我想在Android上的应用上播放静态HLS内容(不是直播视频内容)。我目前所做的是从.m3u8文件下载所有段并将其合并到一个文件中。当我播放这个文件时,我可以看到正在播放的视频,但它不可寻找。根据此link,在Android上无法搜索.ts文件。

我无法冒险在手机上运行ffmpeg将文件转换为MP4格式。我研究过MP4格式及其原子结构。 我想知道的是,如果有一种简单的方法来创建MP4容器(原子层次结构),它只是简单地引用其数据原子中的.ts段(从子段创建的合并段)( MDAT)

我真的很感激任何帮助/建议。

2 个答案:

答案 0 :(得分:0)

没有副本就不可能。 TS使用带有标头的188字节数据包。这些标题在帧的中间创建中断。在mp4中,帧必须是连续的。

答案 1 :(得分:0)

Android提供支持库,如MediaCodec和MediaExtractor,可提供对低级媒体编码/解码的访问。它使用硬件加速是快速有效的。

以下是我认为有人会假设在Android上进行此操作,除非你可以使用ffmpeg,当然这是资源密集型操作。

1)使用MediaExtractor从文件中提取数据。

2)将提取的数据传递给MediaCodec。

3)使用MediaCodec将输出渲染到曲面(如果是视频)和AudioTrack(如果是音频)。

4)这是最困难的一步:同步音频/视频。我还没有实现这个。但这需要跟踪音频和视频之间的时间同步。音频将正常播放,如果是视频,您可能需要丢弃一些帧,以使其与音频播放保持同步。

这是使用AudioTrack和Surface分别解码音频/视频并播放它们的代码。 在视频解码的情况下,有一个睡眠来减慢帧渲染。

public void decodeVideo(Surface surface) throws IOException {
    MediaExtractor extractor = new MediaExtractor();
    MediaCodec codec;
    ByteBuffer[] codecInputBuffers;
    ByteBuffer[] codecOutputBuffers;

    extractor.setDataSource(file);

    Log.d(TAG, "No of tracks = " + extractor.getTrackCount());
    MediaFormat format = extractor.getTrackFormat(0);

    String mime = format.getString(MediaFormat.KEY_MIME);
    Log.d(TAG, "mime = " + mime);
    Log.d(TAG, "format = " + format);

    codec = MediaCodec.createDecoderByType(mime);
    codec.configure(format, surface, null, 0);
    codec.start();
    codecInputBuffers = codec.getInputBuffers();
    codecOutputBuffers = codec.getOutputBuffers();

    extractor.selectTrack(0);

    final long timeout_in_Us = 5000;
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;
    int noOutputCounter = 0;

    long startMs = System.currentTimeMillis();

    while(!sawOutputEOS && noOutputCounter < 50) {
        noOutputCounter++;
        if(!sawInputEOS) {
            int inputBufIndex = codec.dequeueInputBuffer(timeout_in_Us);

            if(inputBufIndex >= 0) {
                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];

                int sampleSize = extractor.readSampleData(dstBuf, 0);

                long presentationTimeUs = 0;

                if(sampleSize < 0) {
                    Log.d(TAG, "saw input EOS.");
                    sawInputEOS = true;
                    sampleSize = 0;
                } else {
                    presentationTimeUs = extractor.getSampleTime();
                }

                codec.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

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

        }

        int res = codec.dequeueOutputBuffer(info, timeout_in_Us);

        if(res >= 0) {

            if(info.size > 0) {
                noOutputCounter = 0;
            }

            int outputBufIndex = res;

            while(info.presentationTimeUs/1000 > System.currentTimeMillis() - startMs) {
                try {
                    Thread.sleep(5);
                } catch (Exception e) {
                    break;
                }
            }

            codec.releaseOutputBuffer(outputBufIndex, true);

            if((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d(TAG, "saw output EOS.");
                sawOutputEOS = true;
            }

        } else if(res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            codecOutputBuffers = codec.getOutputBuffers();
            Log.d(TAG, "output buffers have changed.");

        } else if(res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat format1 = codec.getOutputFormat();
            Log.d(TAG, "output format has changed to " + format1);
        } else if(res == MediaCodec.INFO_TRY_AGAIN_LATER) {
            Log.d(TAG, "Codec try again returned" + res);
        }
    }

    codec.stop();
    codec.release();
}

private int audioSessionId = -1;
private AudioTrack createAudioTrack(MediaFormat format) {
    int channelConfiguration = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
    int bufferSize = AudioTrack.getMinBufferSize(format.getInteger(MediaFormat.KEY_SAMPLE_RATE), channelConfiguration, AudioFormat.ENCODING_PCM_16BIT) * 8;

    AudioTrack audioTrack;
    if(audioSessionId == -1) {
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, format.getInteger(MediaFormat.KEY_SAMPLE_RATE), channelConfiguration,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
    } else {
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, format.getInteger(MediaFormat.KEY_SAMPLE_RATE), channelConfiguration,
                AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM, audioSessionId);
    }
    audioTrack.play();
    audioSessionId = audioTrack.getAudioSessionId();
    return audioTrack;
}

public void decodeAudio() throws IOException {
    MediaExtractor extractor = new MediaExtractor();
    MediaCodec codec;
    ByteBuffer[] codecInputBuffers;
    ByteBuffer[] codecOutputBuffers;

    extractor.setDataSource(file);

    Log.d(TAG, "No of tracks = " + extractor.getTrackCount());
    MediaFormat format = extractor.getTrackFormat(1);

    String mime = format.getString(MediaFormat.KEY_MIME);
    Log.d(TAG, "mime = " + mime);
    Log.d(TAG, "format = " + format);

    codec = MediaCodec.createDecoderByType(mime);
    codec.configure(format, null, null, 0);
    codec.start();
    codecInputBuffers = codec.getInputBuffers();
    codecOutputBuffers = codec.getOutputBuffers();

    extractor.selectTrack(1);

    AudioTrack audioTrack = createAudioTrack(format);

    final long timeout_in_Us = 5000;
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    boolean sawInputEOS = false;
    boolean sawOutputEOS = false;
    int noOutputCounter = 0;

    while(!sawOutputEOS && noOutputCounter < 50) {
        noOutputCounter++;
        if(!sawInputEOS) {
            int inputBufIndex = codec.dequeueInputBuffer(timeout_in_Us);

            if(inputBufIndex >= 0) {
                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];

                int sampleSize = extractor.readSampleData(dstBuf, 0);

                long presentationTimeUs = 0;

                if(sampleSize < 0) {
                    Log.d(TAG, "saw input EOS.");
                    sawInputEOS = true;
                    sampleSize = 0;
                } else {
                    presentationTimeUs = extractor.getSampleTime();
                }

                codec.queueInputBuffer(inputBufIndex, 0, sampleSize, presentationTimeUs, sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

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

        }

        int res = codec.dequeueOutputBuffer(info, timeout_in_Us);
        if(res >= 0) {

            if(info.size > 0) {
                noOutputCounter = 0;
            }

            int outputBufIndex = res;
            //Possibly store the decoded buffer
            ByteBuffer buf = codecOutputBuffers[outputBufIndex];
            final byte[] chunk = new byte[info.size];
            buf.get(chunk);
            buf.clear();

            if(chunk.length > 0) {
                audioTrack.write(chunk, 0 ,chunk.length);
            }

            codec.releaseOutputBuffer(outputBufIndex, false);

            if((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                Log.d(TAG, "saw output EOS.");
                sawOutputEOS = true;
            }

        } else if(res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            codecOutputBuffers = codec.getOutputBuffers();
            Log.d(TAG, "output buffers have changed.");

        } else if(res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat format1 = codec.getOutputFormat();
            Log.d(TAG, "output format has changed to " + format1);
            audioTrack.stop();
            audioTrack = createAudioTrack(codec.getOutputFormat());
        } else if(res == MediaCodec.INFO_TRY_AGAIN_LATER) {
            Log.d(TAG, "Codec try again returned" + res);
        }
    }

    codec.stop();
    codec.release();
}