android - MediaMuxer 无法制作可流式传输的 MP4

标签 android video mediamuxer mediaextractor

我在 Android 上使用 MediaExtractor 来编辑 MP4 以获取音频和视频轨道,然后使用 MediaMuxer 创建一个新文件。它工作正常。我可以在手机(和其他播放器)上播放新的 MP4,但无法在网络上播放文件。当我停止 MediaMuxer 时,它会生成一条日志消息

"The mp4 file will not be streamable."

我查看了底层 native 代码 (MPEG4Writer.cpp),作者似乎无法计算所需的 moov 框大小。如果未将比特率作为参数提供给编写器,它会尝试使用一些启发式进行猜测。问题是 MediaMuxer 不提供设置 MPEG4Writer 参数的能力。我是否遗漏了什么或者我是否一直在寻找其他生成文件(或标题)的方法?谢谢。

最佳答案

在 MPEG4Writer.cpp 中:

// The default MIN_MOOV_BOX_SIZE is set to 0.6% x 1MB / 2,
// where 1MB is the common file size limit for MMS application.
// The default MAX _MOOV_BOX_SIZE value is based on about 3
// minute video recording with a bit rate about 3 Mbps, because
// statistics also show that most of the video captured are going
// to be less than 3 minutes.

对于 MediaMuxer 的使用方式,这是一个错误的假设。我们正在录制最多 15 秒的高分辨率视频,而 MIN_MOOV_BOX_SIZE 太小了。因此,为了使文件可流化,我必须重写文件以将 moov header 移动到 mdat 之前并修补一些偏移量。这是我的代码。这不是很好。错误路径未正确处理,并且它对框的顺序进行了假设。

public void fastPlay(String srcFile, String dstFile)  {
    RandomAccessFile inFile = null;
    FileOutputStream outFile = null;
    try {
        inFile = new RandomAccessFile(new File(srcFile), "r");
        outFile = new FileOutputStream(new File(dstFile));
        int moovPos = 0;
        int mdatPos = 0;
        int moovSize = 0;
        int mdatSize = 0;
        byte[] boxSizeBuf = new byte[4];
        byte[] pathBuf = new byte[4];
        int boxSize;
        int dataSize;
        int bytesRead;
        int totalBytesRead = 0;
        int bytesWritten = 0;

        // First find the location and size of the moov and mdat boxes
        while (true) {
            try {
                boxSize = inFile.readInt();
                bytesRead = inFile.read(pathBuf);
                if (bytesRead != 4) {
                    Log.e(TAG, "Unexpected bytes read (path) " + bytesRead);
                    break;
                }
                String pathRead = new String(pathBuf, "UTF-8");
                dataSize = boxSize - 8;
                totalBytesRead += 8;
                if (pathRead.equals("moov")) {
                    moovPos = totalBytesRead - 8;
                    moovSize = boxSize;
                } else if (pathRead.equals("mdat")) {
                    mdatPos = totalBytesRead - 8;
                    mdatSize = boxSize;
                }
                totalBytesRead += inFile.skipBytes(dataSize);
            } catch (IOException e) {
                break;
            }
        }

        // Read the moov box into a buffer. This has to be patched up. Ug.
        inFile.seek(moovPos);
        byte[] moovBoxBuf = new byte[moovSize]; // This shouldn't be too big.
        bytesRead = inFile.read(moovBoxBuf);
        if (bytesRead != moovSize) {
            Log.e(TAG, "Couldn't read full moov box");
        }

        // Now locate the stco boxes (chunk offset box) inside the moov box and patch
        // them up. This ain't purdy.
        int pos = 0;
        while (pos < moovBoxBuf.length - 4) {
            if (moovBoxBuf[pos] == 0x73 && moovBoxBuf[pos + 1] == 0x74 &&
                    moovBoxBuf[pos + 2] == 0x63 && moovBoxBuf[pos + 3] == 0x6f) {
                int stcoPos = pos - 4;
                int stcoSize = byteArrayToInt(moovBoxBuf, stcoPos);
                patchStco(moovBoxBuf, stcoSize, stcoPos, moovSize);
            }
            pos++;
        }

        inFile.seek(0);
        byte[] buf = new byte[(int) mdatPos];
        // Write out everything before mdat
        inFile.read(buf);
        outFile.write(buf);

        // Write moov
        outFile.write(moovBoxBuf, 0, moovSize);

        // Write out mdat
        inFile.seek(mdatPos);
        bytesWritten = 0;
        while (bytesWritten < mdatSize) {
            int bytesRemaining = (int) mdatSize - bytesWritten;
            int bytesToRead = buf.length;
            if (bytesRemaining < bytesToRead) bytesToRead = bytesRemaining;
            bytesRead = inFile.read(buf, 0, bytesToRead);
            if (bytesRead > 0) {
                outFile.write(buf, 0, bytesRead);
                bytesWritten += bytesRead;
            } else {
                break;
            }
        }
    } catch (IOException e) {
        Log.e(TAG, e.getMessage());
    } finally {
        try {
            if (outFile != null) outFile.close();
            if (inFile != null) inFile.close();
        } catch (IOException e) {}
    }
}

private void patchStco(byte[] buf, int size, int pos, int moovSize) {
    Log.e(TAG, "stco " + pos + " size " + size);
    // We are inserting the moov box before the mdat box so all of
    // offsets in the stco box need to be increased by the size of the moov box. The stco
    // box is variable in length. 4 byte size, 4 byte path, 4 byte version, 4 byte flags
    // followed by a variable number of chunk offsets. So subtract off 16 from size then
    // divide result by 4 to get the number of chunk offsets to patch up.
    int chunkOffsetCount = (size - 16) / 4;
    int chunkPos = pos + 16;
    for (int i = 0; i < chunkOffsetCount; i++) {
        int chunkOffset = byteArrayToInt(buf, chunkPos);
        int newChunkOffset = chunkOffset + moovSize;
        intToByteArray(newChunkOffset, buf, chunkPos);
        chunkPos += 4;
    }
}

public static int byteArrayToInt(byte[] b, int offset)
{
    return   b[offset + 3] & 0xFF |
            (b[offset + 2] & 0xFF) << 8 |
            (b[offset + 1] & 0xFF) << 16 |
            (b[offset] & 0xFF) << 24;
}

public void intToByteArray(int a, byte[] buf, int offset)
{
    buf[offset] = (byte) ((a >> 24) & 0xFF);
    buf[offset + 1] = (byte) ((a >> 16) & 0xFF);
    buf[offset + 2] = (byte) ((a >> 8) & 0xFF);
    buf[offset + 3] = (byte) (a  & 0xFF);
}

关于android - MediaMuxer 无法制作可流式传输的 MP4,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24000026/

相关文章:

android - 如果我们不手动设置此值,将在 Android MediaCodec 中设置哪个 AVC 配置文件/级别?

android - 从 Lottie JSON 文件制作视频并使用 FFMPEG 将其与原始视频叠加的问题

android - 在 android 中将 SurfaceView 动画录制为视频文件

代码中出现 java.classCastException

android - 如何为 firebase 性能监控创建空操作版本

Android Lint 内容描述警告

javascript - 阻止 IE/Edge 预加载视频

ios - Flutter video_player 从头开始​​重新启动视频

android - Spinner 不应用 dropDownSelector 属性

php - 如何在 PHP 中将上传的视频文件编码为 .flv 格式