【问题标题】:Create MP4 container for .ts segment为 .ts 段创建 MP4 容器
【发布时间】:2014-12-09 23:02:57
【问题描述】:

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

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

如果有任何帮助/建议,我将不胜感激。

【问题讨论】:

    标签: android video http-live-streaming


    【解决方案1】:

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

    【讨论】:

    • 感谢您的回复。我想这是有道理的,为什么我不能玩它。你知道是否有办法解码 TS 文件,获取帧,然后用它来创建 MP4。我知道ffmpeg就是这样做的,但是它的源代码非常复杂。此外,我正在考虑有一个基于 JAVA 的实现。在您看来,有没有更好的方法来处理这个问题?
    • 你不想解码。你只想转换。我不知道 Java 中有哪些库可用,但您可以随时编写自己的库。
    • 谢谢。这就是我打算做的。您是否知道任何可能对 trasmux 有用的参考资料。或者,如果您可以建议如何进行。所有这些视频格式对我来说都是新的。我的意思是,我一直在使用这些格式,但只是为了播放视频,从来没有这么细化过。
    • 传输流中的维基百科文章我非常好。您还需要了解基本流格式。 Mp3 帧头、ADTS 头、序列头/额外数据。等等。我在这里有一篇关于 AVCVC 的帖子 szatmary.org/blog/25
    • 感谢您的参考。这是我打算做的。如果我错了,请纠正我。首先,我需要从 TS 文件(音频和视频)中获取基本流。其次,弄清楚如何将这些流放在 MP4 容器中。这种方法行得通吗?
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2013-02-24
      • 2020-09-14
      • 2013-07-09
      • 1970-01-01
      • 2016-12-12
      • 1970-01-01
      • 2015-06-27
      • 1970-01-01
      • 2023-03-26
      相关资源
      最近更新 更多