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 にアップロードすると、そこでも完全に正しく再生できます。
ありがとう!マーク