簡體   English   中英

從Android屏幕錄像機捕獲的視頻無法在Web瀏覽器上播放

[英]Video captured from Android screen recorder can not be played on web browser

當我嘗試在組件中的Web瀏覽器上播放視頻時遇到問題,該文件根本無法播放。 該文件是使用MediaRecorder和MediaProjection在Android設備上捕獲的,並試圖記錄屏幕。 這是我初始化MediaRecorder的代碼:

public class ScreenRecordService extends Service {

private static final String TAG = ScreenRecordService.class.getSimpleName();

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final int DISPLAY_WIDTH = 960;
private static final int DISPLAY_HEIGHT = 540;

private float mDensity;
private int mRotation;
private boolean mIsRecording;

private MediaProjectionManager mProjectionManager;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private MediaProjectionCallback mMediaProjectionCallback;
private MediaRecorder mMediaRecorder;
private String mFilePath;

static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

private class MediaProjectionCallback extends MediaProjection.Callback {
    @Override
    public void onStop() {
        try {
            if (mIsRecording) {
                mIsRecording = false;
                mMediaRecorder.stop();
                mMediaRecorder.reset();
            }
            mMediaProjection = null;
            stopScreenSharing();

            HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_STOP_SUCCESS));
        } catch (Exception e) {
            e.printStackTrace();
            HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_STOP_FAIL));
        }
    }
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onStopCall(EventRecorder.Client clientEvent) {
    if (clientEvent.messageType == EventRecorder.CLIENT_STOP_RECORD) {
        stopRecording();
    }
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    super.onCreate();
    HermesEventBus.getDefault().register(this);
    AppManager.getInstance().addService(this);
    mProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
    mMediaProjectionCallback = new MediaProjectionCallback();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (mProjectionManager == null) {
        mProjectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
    }
    if (intent != null) {
        mDensity = intent.getFloatExtra("density", 0f);
        mRotation = intent.getIntExtra("rotation", 0);
        mFilePath = intent.getStringExtra(Const.Intent.INFO);
        JLog.d(TAG, mFilePath);

        startRecording(intent);
    }
    return START_NOT_STICKY;
}

@Override
public void onDestroy() {
    super.onDestroy();
    AppManager.getInstance().removeService(this);
}

private void startRecording(Intent intent) {
    try {
        if (!mIsRecording) {
            mMediaProjection = mProjectionManager.getMediaProjection(RESULT_OK, intent);
            mMediaProjection.registerCallback(mMediaProjectionCallback, null);
            initRecorder();
            mVirtualDisplay = createVirtualDisplay();
            mMediaRecorder.start();
            mIsRecording = true;

            HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_START_SUCCESS));
        }
    } catch (Exception e) {
        e.printStackTrace();
        mIsRecording = false;
        HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_START_FAIL));
    }
}

private void stopRecording() {
    try {
        if (mIsRecording) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            stopScreenSharing();

            HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_STOP_SUCCESS));
        }
    } catch (Exception e) {
        e.printStackTrace();
        mIsRecording = false;
        if (mMediaRecorder != null) {
            mMediaRecorder.reset();
        }
        stopScreenSharing();
        HermesEventBus.getDefault().post(new EventRecorder.Server(EventRecorder.SERVER_STOP_FAIL));
    }
}

private VirtualDisplay createVirtualDisplay() {
    return mMediaProjection.createVirtualDisplay(getString(R.string.video_record), DISPLAY_WIDTH, DISPLAY_HEIGHT, (int) mDensity,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mMediaRecorder.getSurface(), null, null);
}

private void stopScreenSharing() {
    if (mVirtualDisplay == null) {
        return;
    }
    mVirtualDisplay.release();
    destroyMediaProjection();
    mIsRecording = false;
}

private void initRecorder() {
    int bitRateQuality = PrefsUtils.getInstance(this, Const.Pref.FILE_COMMON).getInt(Const.Pref.KEY_RECORD_BITRATE, Const.Setting.QUALITY_MID);
    int bitRate;
    if (bitRateQuality == Const.Setting.QUALITY_HIGH) {
        bitRate = 1536000;
    } else if (bitRateQuality == Const.Setting.QUALITY_MID) {
        bitRate = 1024 * 1024;
    } else {
        bitRate = 512000;
    }
    if (mMediaRecorder == null) {
        mMediaRecorder = new MediaRecorder();
    }
    try {
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); //THREE_GPP
        mMediaRecorder.setOutputFile(mFilePath);
        mMediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setVideoFrameRate(8); // 30
        mMediaRecorder.setVideoEncodingBitRate(bitRate);
        int orientation = ORIENTATIONS.get(mRotation + 90);
        mMediaRecorder.setOrientationHint(orientation);
        mMediaRecorder.prepare();
        mMediaRecorder.setOnInfoListener((mr, what, extra) -> {
            if (what == MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
                stopRecording();
            }
        });
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private void destroyMediaProjection() {
    if (mMediaProjection != null) {
        mMediaProjection.unregisterCallback(mMediaProjectionCallback);
        mMediaProjection.stop();
        mMediaProjection = null;
    }
    JLog.i(TAG, "MediaProjection Stopped");
}

}

這是我上傳的文件。

http://eachdoctorvideotest.oss-cn-shenzhen.aliyuncs.com/1103/videoRecord/input/ali_TVM1103vRecordIn20190212154904841.mp4

只需將網址粘貼到任何瀏覽器中(我使用Chrome,Chrome無法播放,但是Safari可以播放),您會發現該文件無法播放。 但是您可以在PC上的任何第三方媒體播放器上播放它。 那么,該文件不能在瀏覽器中播放的確切問題是什么?

該視頻文件最初是由兩個文件(視頻軌道和音頻軌道)生成的。 我使用mp4parser合並了曲目,您可能會在此處看到該庫:

https://github.com/sannies/mp4parser

這是我用來組合它們的關鍵代碼:

    public boolean muxAacMp4(String mp4Path, String aacPath, String outPath) {
    boolean flag = false;
    try {
        AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath));
        Movie videoMovie = MovieCreator.build(mp4Path);
        Track videoTracks = null;
        for (Track videoMovieTrack : videoMovie.getTracks()) {
            if ("vide".equals(videoMovieTrack.getHandler())) {
                videoTracks = videoMovieTrack;
            }
        }

        Movie resultMovie = new Movie();
        resultMovie.addTrack(videoTracks);
        resultMovie.addTrack(aacTrack);

        Container out = new DefaultMp4Builder().build(resultMovie);
        FileOutputStream fos = new FileOutputStream(new File(outPath));
        out.writeContainer(fos.getChannel());
        fos.close();
        flag = true;
        Log.e("update_tag", "merge finish");
    } catch (Exception e) {
        e.printStackTrace();
        flag = false;
    }
    return flag;
}

如果您在HTML5視頻元素上放置了'error'處理程序,則可以看到此文件產生以下錯誤(Chrome 71):

Error 3; details: PIPELINE_ERROR_DECODE: Failed to send audio packet for decoding: timestamp=0 duration=32000 size=2 side_data_size=0 is_key_frame=1 encrypted=0 discard_padding (us)=(0, 0)

(供參考:類似的錯誤是在github討論此處 )。

2字節對於音頻樣本來說有點小。 稍作挖掘就會發現,它實際上是您的音軌的“音頻特定配置”的副本,這很奇怪,因為該信息已經存在於.mp4標頭中。 它正在復制到時間戳0的樣本(第一個樣本)中; 我不知道為什么。

您可能需要看一下setAudioEncoder()的文檔; 您尚未調用它,並且文檔說明:

如果未調用此方法,則輸出文件將不包含音軌。

但是,您的文件包含一個音軌。 因此,這可能需要進一步調查。

編輯

有了對您的問題的新理解,看來最方便的解決方案就是強行從AAC流中刪除第一個樣本。 也可以使用您的“組合”代碼來完成。 我會這樣子類化AACTrackImpl

AACTrackImpl aacTrack = new AACTrackImpl(new FileDataSourceImpl(aacPath)) {
    boolean mAltered = false;
    @Override
    List<Sample> getSamples() {
        List<Samples> samples = super.getSamples();
        if(!mAltered)
        {
            samples.remove(0);
            mAltered=true;
        }
        return samples;
    }
};

我尚未測試此代碼。 確實是一個很糟糕的解決方案,它取決於許多假設。 它利用了AAC軌道中的所有樣本都具有相同的“持續時間”這一事實。 否則,您也必須使用類似的技術來重寫getSampleDurations()

由於我們刪除了一個樣本但沒有更改時間戳,因此這將使您的所有音頻偏移約23ms。 在這種情況下,由於我們不完全知道您的音頻編碼器為何首先表現出這種行為,因此可以將其解釋為造成時序問題或解決問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM