使用Camera2

时间:2016-07-08 18:05:09

标签: javascript android html5-video cordova-plugins video-encoding

我的团队正在使用Cordova框架开发移动网络应用程序,最初定位到Android。该应用程序的一个功能是通过自定义媒体捕获插件在用户手机上录制视频,在本地保存,使用Cordova文件插件(cordova-plugin-file)读取,并将其流式传输到Node JS服务器,通过Stream API分发给其他连接的用户。

接收流的设备将每个传入的块保存在ArrayBuffers数组中,然后通过Blob构造函数将其转换为Blob:

let receivedChunks: ArrayBuffer[] = [];

// chunks of video data received from node.js server
// each chunk saved as ArrayBuffer
// ArrayBuffer[] treated as blob parts

const videoBlob = new Blob( receivedChunks, { type: 'video/mp4' });

然后我们使用File插件将此blob写入我们应用程序的Android cacheDirectory,并获取file:/// URL以将视频加载到HTML5 <video>元素中。该应用使用媒体API和媒体事件排队播放这些<video>元素。

所发布的Cordova(或PhoneGap)插件都不适合我们的UI要求,因此我们根据Camera2 API编写了我们自己的插件(我们暂时取消了对Android 4.x及更低版本的支持)。我们的插件基于Google示例,并且在我们遇到另一个StackOverflow用户引用的相同问题之前一直正常运行:Camera2 video recording without preview on Android: mp4 output file not fully playable

事实证明,运行Android 6.0 Marshmallow的三星Galaxy设备上的Deep Sleep存在一些问题。我实现了我在回答这个问题时所描述的解决方法,这部分解决了问题,但是给我们留下了混乱的元数据,这意味着我们丢失了设备方向提示(我们的应用使用sensorLandscape来保持用户界面正确的方式,所以我们必须对录制的视频应用方向修正,以防止它们颠倒播放。

因此我们进一步采取了解决方法并决定重新编码已更正的视频:

private void transcodeVideo(String pathToVideo)
        throws IOException {

    MediaExtractor extractor = new MediaExtractor();
    extractor.setDataSource(pathToVideo);
    int trackCount = extractor.getTrackCount();

    MediaMuxer muxer = new MediaMuxer(pathToVideo+ "transcoded", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

    HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
    for (int i = 0; i < trackCount; i++) {
        extractor.selectTrack(i);
        MediaFormat format = extractor.getTrackFormat(i);
        int dstIndex = muxer.addTrack(format);
        indexMap.put(i, dstIndex);
    }

    boolean sawEOS = false;
    int bufferSize = 256 * 1024;
    int frameCount = 0;
    int offset = 100;

    ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    muxer.setOrientationHint(DEFAULT_ORIENTATIONS.get(_savedVideoRotation));

    muxer.start();

    while (!sawEOS) {
        bufferInfo.offset = offset;
        bufferInfo.size = extractor.readSampleData(dstBuf, offset);
        if (bufferInfo.size < 0) {

                Log.d(TAG, "saw input EOS.");

            sawEOS = true;
            bufferInfo.size = 0;
        } else {
            bufferInfo.presentationTimeUs = extractor.getSampleTime();
            bufferInfo.flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
            int trackIndex = extractor.getSampleTrackIndex();
            muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
                    bufferInfo);
            extractor.advance();
            frameCount++;

                Log.d(TAG, "Frame (" + frameCount + ") " +
                        "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
                        " Flags:" + bufferInfo.flags +
                        " TrackIndex:" + trackIndex +
                        " Size(KB) " + bufferInfo.size / 1024);
        }
    }
    muxer.stop();
    muxer.release();
}

这就是事情变得奇怪的地方。

重新编码的视频似乎在大多数其他设备上播放得很好,包括相当长的Moto G LTE(第一代)。但是,当我们在Moto G上同时流式传输并保存多个视频时,重新编码的视频将无法正常播放。没有音频或视频,但<video>元素会发出我们希望看到视频播放正常的所有正常视频事件 - 尤其是'ended'事件在预期的持续时间后被解雇。

如果我们只播放和保存两个视频,Moto G可以播放重新编码的视频。同一会话中的其他设备(都接收从服务器传送的同一组视频)似乎对S7的重新编码视频没有任何问题。如果我们从同一会话中的设备集中删除S7,我们有时会看到相同的问题,但有时不会 - 但是当涉及重新编码的视频的S7时,它会100%一致。

我们的MP4编码有什么明显错误吗?是否有人知道同时将多个文件写入较慢的Android设备(如Moto G)的闪存存储器的问题?有没有其他人看到这种奇怪的播放行为,其中视频元素在没有实际播放音频或视频的情况下触发媒体事件?

我知道这个问题可能有点缺乏重点,并且涉及很多变量(代码中有多个可能的失败点,多个设备,不清楚问题是编码,回放还是别的东西),但如果它为任何人敲响了钟声,他们可以提供一点点洞察力,那么我们将不胜感激!

0 个答案:

没有答案