14

Samsung Galaxy S6 (約 30 fps で 1920x1080 をサポート) の背面カメラ (顔に面しているカメラ) からビデオを録画しようとしています。これはバックグラウンドで発生するだけなので、プレビューする必要がない場合は、プレビューにサーフェスを使用する必要はありません。

動作しているように見えますが、出力ファイルは実際には正しい方法で再生できません。私の Windows 10 PC では、Windows Media Player は最初のフレームを表示してからオーディオを再生しますが、VLC はどのフレームも表示しません。私の電話では、記録されたファイルは再生可能ですが、完全には再生できません。最初のフレームを 5 ~ 8 秒間保持し、最後に残り時間が 0 になり、表示される合計時間が変化してから、実際のビデオ フレームの再生が開始されます。私の Mac (10.9.5) では、Quicktime はビデオを表示しませんが (エラーはありません)、それでも Google Picasa は完全に再生できます。自分の PC で Picasa を試して動作するかどうかを確認したかったのですが、もう Google Picasa をダウンロードできませんでした。

見つけた Windows 用のコーデック パックをインストールしてみましたが、何も解決しませんでした。MediaInfo v0.7.85 は、ファイルについて次のように報告しています。

全般的
完全な名前: C:\...\1465655479915.mp4
フォーマット:MPEG-4
フォーマット プロファイル : ベース メディア / バージョン 2
コーデック ID : mp42 (isom/mp42)
ファイルサイズ : 32.2 MiB
持続時間 : 15 秒 744 ミリ秒
全体のビットレート: 17.1 Mbps
エンコードされた日付: UTC 2016-06-11 14:31:50
タグ付けされた日付 : UTC 2016-06-11 14:31:50
com.android.バージョン: 6.0.1

ビデオ
ID : 1
フォーマット:AVC
フォーマット/情報 : 高度なビデオ コーデック
フォーマットプロファイル:High@L4
フォーマット設定、CABAC : あり
フォーマット設定、ReFrames : 1 フレーム
フォーマット設定、GOP:M=1、N=30
コーデック ID : avc1
コーデック ID/情報 : 高度なビデオ コーディング
持続時間 : 15 秒 627 ミリ秒
ビットレート:16.2Mbps
幅 : 1 920 ピクセル
高さ : 1 080 ピクセル
ディスプレイアスペクト比:16:9
フレームレートモード:可変
フレームレート : 0.000 (0/1000) fps
最小フレームレート: 0.000 fps
最大フレームレート: 30.540 fps
色空間:YUV
彩度サブサンプリング: 4:2:0
ビット深度:8ビット
スキャンタイプ: プログレッシブ
ストリームサイズ: 0.00 バイト (0%)
ソース ストリーム サイズ: 31.7 MiB (98%)
タイトル : ビデオハンドル
言語 : 英語
エンコードされた日付: UTC 2016-06-11 14:31:50
タグ付けされた日付 : UTC 2016-06-11 14:31:50
mdhd_Duration : 15627

オーディオ
ID : 2
フォーマット:AAC
フォーマット/情報 : 高度なオーディオ コーデック
フォーマットプロファイル:LC
コーデックID : 40
持続時間 : 15 秒 744 ミリ秒
ビットレートモード:一定
ビットレート:256Kbps
チャンネル : 2 チャンネル
チャンネル位置 : フロント: LR
サンプリングレート:48.0KHz
フレームレート : 46.875 fps (1024 spf)
圧縮モード: 非可逆
ストリームサイズ : 492 KiB (1%)
タイトル : サウンドハンドル
言語 : 英語
エンコードされた日付: UTC 2016-06-11 14:31:50
タグ付けされた日付 : UTC 2016-06-11 14:31:50

これを作成するために使用しているコードは次のとおりです。

package invisiblevideorecorder;

import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.view.Surface;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

/**
 * @author Mark
 * @since 6/10/2016
 */
public class InvisibleVideoRecorder {
    private static final String TAG = "InvisibleVideoRecorder";
    private final CameraCaptureSessionStateCallback cameraCaptureSessionStateCallback = new CameraCaptureSessionStateCallback();
    private final CameraDeviceStateCallback cameraDeviceStateCallback = new CameraDeviceStateCallback();
    private MediaRecorder mediaRecorder;
    private CameraManager cameraManager;
    private Context context;

    private CameraDevice cameraDevice;

    private HandlerThread handlerThread;
    private Handler handler;

    public InvisibleVideoRecorder(Context context) {
        this.context = context;
        handlerThread = new HandlerThread("camera");
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());

        try {
            mediaRecorder = new MediaRecorder();

            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
            mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);

            final String filename = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + System.currentTimeMillis() + ".mp4";
            mediaRecorder.setOutputFile(filename);
            Log.d(TAG, "start: " + filename);

            // by using the profile, I don't think I need to do any of these manually:
//            mediaRecorder.setVideoEncodingBitRate(16000000);
//            mediaRecorder.setVideoFrameRate(30);
//            mediaRecorder.setCaptureRate(30);
//            mediaRecorder.setVideoSize(1920, 1080);
//            mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
//            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

//            Log.d(TAG, "start: 1 " + CamcorderProfile.hasProfile(CameraMetadata.LENS_FACING_BACK, CamcorderProfile.QUALITY_1080P));
            // true
//            Log.d(TAG, "start: 2 " + CamcorderProfile.hasProfile(CameraMetadata.LENS_FACING_BACK, CamcorderProfile.QUALITY_HIGH_SPEED_1080P));
            // false
//            Log.d(TAG, "start: 3 " + CamcorderProfile.hasProfile(CameraMetadata.LENS_FACING_BACK, CamcorderProfile.QUALITY_HIGH));
            // true

            CamcorderProfile profile = CamcorderProfile.get(CameraMetadata.LENS_FACING_BACK, CamcorderProfile.QUALITY_1080P);
            Log.d(TAG, "start: profile " + ToString.inspect(profile));
//          start: 0 android.media.CamcorderProfile@114016694 {
//                audioBitRate: 256000
//                audioChannels: 2
//                audioCodec: 3
//                audioSampleRate: 48000
//                duration: 30
//                fileFormat: 2
//                quality: 6
//                videoBitRate: 17000000
//                videoCodec: 2
//                videoFrameHeight: 1080
//                videoFrameRate: 30
//                videoFrameWidth: 1920
//            }
            mediaRecorder.setOrientationHint(0);
            mediaRecorder.setProfile(profile);
            mediaRecorder.prepare();
        } catch (IOException e) {
            Log.d(TAG, "start: exception" + e.getMessage());
        }

    }

    public void start() {
        Log.d(TAG, "start: ");

        cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraManager.openCamera(String.valueOf(CameraMetadata.LENS_FACING_BACK), cameraDeviceStateCallback, handler);
        } catch (CameraAccessException | SecurityException e) {
            Log.d(TAG, "start: exception " + e.getMessage());
        }

    }

    public void stop() {
        Log.d(TAG, "stop: ");
        mediaRecorder.stop();
        mediaRecorder.reset();
        mediaRecorder.release();
        cameraDevice.close();
        try {
            handlerThread.join();
        } catch (InterruptedException e) {

        }
    }

    private class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback {
        private final static String TAG = "CamCaptSessionStCb";

        @Override
        public void onActive(CameraCaptureSession session) {
            Log.d(TAG, "onActive: ");
            super.onActive(session);
        }

        @Override
        public void onClosed(CameraCaptureSession session) {
            Log.d(TAG, "onClosed: ");
            super.onClosed(session);
        }

        @Override
        public void onConfigured(CameraCaptureSession session) {
            Log.d(TAG, "onConfigured: ");
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            Log.d(TAG, "onConfigureFailed: ");
        }

        @Override
        public void onReady(CameraCaptureSession session) {
            Log.d(TAG, "onReady: ");
            super.onReady(session);
            try {
                CaptureRequest.Builder builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
                builder.addTarget(mediaRecorder.getSurface());
                CaptureRequest request = builder.build();
                session.setRepeatingRequest(request, null, handler);
                mediaRecorder.start();
            } catch (CameraAccessException e) {
                Log.d(TAG, "onConfigured: " + e.getMessage());

            }
        }

        @Override
        public void onSurfacePrepared(CameraCaptureSession session, Surface surface) {
            Log.d(TAG, "onSurfacePrepared: ");
            super.onSurfacePrepared(session, surface);
        }
    }

    private class CameraDeviceStateCallback extends CameraDevice.StateCallback {
        private final static String TAG = "CamDeviceStateCb";

        @Override
        public void onClosed(CameraDevice camera) {
            Log.d(TAG, "onClosed: ");
            super.onClosed(camera);
        }

        @Override
        public void onDisconnected(CameraDevice camera) {
            Log.d(TAG, "onDisconnected: ");
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            Log.d(TAG, "onError: ");
        }

        @Override
        public void onOpened(CameraDevice camera) {
            Log.d(TAG, "onOpened: ");
            cameraDevice = camera;
            try {
                camera.createCaptureSession(Arrays.asList(mediaRecorder.getSurface()), cameraCaptureSessionStateCallback, handler);
            } catch (CameraAccessException e) {
                Log.d(TAG, "onOpened: " + e.getMessage());
            }
        }
    }

}

camera2 API はまだ十分に文書化されていないため、これを理解するために、Android ソース (テストおよびアプリケーション) コードと、github で見つけたいくつかの例に従いました。

私が間違っていることは明らかですか?それとも、Quicktime を使用するための Mac と、Windows Media Player および VLC を使用するための PC のコーデックが不足しているだけですか? 私はまだ Linux でファイルを再生しようとしていないので、そこで何が起こるかはまだわかりません。ああ、mp4 ファイルを photos.google.com にアップロードすると、そこでも完全に正しく再生できます。

ありがとう!マーク

4

2 に答える 2

9

私のチームは、Camera2 API に基づくプラグインを開発していたときに同様の問題に遭遇しましたが、Samsung Galaxy S7 にのみ影響を与えました (この動作を示さなかったテスト用の S6 もあります)。

この問題は、Samsung のカメラ ファームウェアのバグが原因であると考えられ、デバイスがディープ スリープ (Android 6.0 Marshmallow の超低電力モード) から復帰したときにトリガーされました。ディープ スリープから復帰した後、Camera2 MediaRecorder を使用してキャプチャおよびエンコードされたビデオの最初のフレームは、非常に長いフレーム持続時間 (ビデオ自体の合計持続時間と同じか、それより長い場合もあります) を持ちます。

その結果、再生時に最初のフレームが表示され、オーディオが再生され続けます。最初のフレームの表示が終了すると、残りのフレームが通常どおり再生されます。

同様の問題を抱えている他の人がGitHub でこの問題について話し合っているのを見つけました

この問題は、Marshmallow を実行している一部のデバイスでのディープ スリープの問題です。Verizon の S7 には問題がなく、AT&T の S7 には問題があるため、CPU に関連しているようです。Marshmallow に更新されたときに、S6 Verizon 電話でこれを見たことがあります。

複製するには、USB に接続した状態でデバイスを再起動します。サンプルを実行します。すべて問題ないはずです。次に、デバイスの接続を外し、ディープ スリープ状態 (画面オフ、5 分間動かない状態) にしてから、もう一度試してください。デバイスがディープ スリープ状態になると、この問題が発生します。

最終的に、 cybaker の提案された回避策を使用しました。つまり、ビデオ ファイルの作成時に、ビデオの最初のフレームの長さを調べます。正しくないと思われる場合は、適切なフレーム期間でビデオを再エンコードします。

DataSource channel = new FileDataSourceImpl(rawFile);
IsoFile isoFile = new IsoFile(channel);

List<TrackBox> trackBoxes = isoFile.getMovieBox().getBoxes(TrackBox.class);
boolean sampleError = false;
for (TrackBox trackBox : trackBoxes) {
    TimeToSampleBox.Entry firstEntry = trackBox.getMediaBox().getMediaInformationBox().getSampleTableBox().getTimeToSampleBox().getEntries().get(0);

    // Detect if first sample is a problem and fix it in isoFile
    // This is a hack. The audio deltas are 1024 for my files, and video deltas about 3000
    // 10000 seems sufficient since for 30 fps the normal delta is about 3000
    if(firstEntry.getDelta() > 10000) {
        sampleError = true;
        firstEntry.setDelta(3000);
    }
}

if(sampleError) {
    Movie movie = new Movie();
    for (TrackBox trackBox : trackBoxes) {
            movie.addTrack(new Mp4TrackImpl(channel.toString() + "[" + trackBox.getTrackHeaderBox().getTrackId() + "]" , trackBox));
    }
    movie.setMatrix(isoFile.getMovieBox().getMovieHeaderBox().getMatrix());
    Container out = new DefaultMp4Builder().build(movie);

    //delete file first!
    FileChannel fc = new RandomAccessFile(rawFile.getName(), "rw").getChannel();
    out.writeContainer(fc);
    fc.close();
    Log.d(TAG, "Finished correcting raw video");
}

これがあなたを正しい方向に向けてくれることを願っています!

于 2016-07-08T14:03:10.807 に答える