为 .ts 段创建 MP4 容器



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

我不能冒险在手机上运行 ffmpeg 将文件转换为 MP4 格式。我研究了MP4格式及其原子结构。我想知道的是,是否有一种简单的方法可以创建 MP4 容器(原子层次结构(,该容器将简单地引用其数据原子 (mdat( 中的 .ts 段(从子段创建的合并段(

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

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

Android 提供了 MediaCodec 和 MediaExtractor 等支持库,提供对低级媒体编码/解码的访问。它使用硬件加速,因此快速高效。

以下是我认为应该在Android上执行此操作的方式,除非您可以使用ffmpeg,当然,如果资源密集型操作。

1( 使用媒体提取器从文件中提取数据。

2(将提取的数据传递给媒体编解码器。

3( 使用媒体编解码器将输出渲染到表面(如果是视频(和音频跟踪(如果是音频(。

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();
}

相关内容

  • 没有找到相关文章

最新更新