MediaMuxer иногда приводит к черному видео

Я использую AndroidEncoder для создания видео H264 со звуком AAC, которое я отправляю как по сети RTMP, так и сохраняю локально в файле MP4 с помощью MediaMuxer.

Вещание хорошо работает по RTMP, но при локальном сохранении результирующее видео иногда нормальное, иногда просто черные кадры со звуком (или просто первый неполный кадр с какими-то блоками информации), а иногда воспроизводится сначала звук, потом видео со случайными скоростями FPS.

Это вывод, показанный моим классом-оболочкой Muxer и классом MPEG4Writer SDK:

02-28 11:57:38.521 6420-6475/com.myapp.broadcast W/AndroidMuxer: addTrack: Requested adding of track to class AndroidMuxer of type AUDIO
02-28 11:57:38.522 6420-6475/com.myapp.broadcast W/AndroidMuxer: addTrack: Adding track for audio/mp4a-latm
02-28 11:57:38.522 6420-6475/com.myapp.broadcast W/AndroidMuxer: addTrack: Track index to class AndroidMuxer of type AUDIO, track saved for future use!
02-28 11:57:38.621 6420-6470/com.myapp.broadcast W/AndroidMuxer: addTrack: Requested adding of track to class AndroidMuxer of type VIDEO
02-28 11:57:38.621 6420-6470/com.myapp.broadcast W/AndroidMuxer: addTrack: Adding track for video/avc
02-28 11:57:38.621 6420-6470/com.myapp.broadcast W/AndroidMuxer: addTrack: All tracks added, marking Local muxer as ready!
02-28 11:57:38.622 6420-6470/com.myapp.broadcast W/AndroidMuxer: addTrack: Track index to class AndroidMuxer of type VIDEO, track saved for future use!
02-28 11:57:52.050 6420-6420/com.myapp.broadcast I/AndroidMuxer: init: Added audio track widh id 0
02-28 11:57:52.050 6420-6420/com.myapp.broadcast I/AndroidMuxer: init: Added video track widh id 1
02-28 11:57:52.050 6420-6420/com.myapp.broadcast W/AndroidMuxer: init: Muxer was successfully created! Starting to record
02-28 11:57:52.056 6420-6420/com.myapp.broadcast I/MPEG4Writer: limits: 4294967295/0 bytes/us, bit rate: -1 bps and the estimated moov size 3195 bytes
02-28 11:57:52.057 6420-6420/com.myapp.broadcast W/AndroidMuxer: start: Muxer started!
02-28 11:57:52.070 6420-6965/com.myapp.broadcast I/MPEG4Writer: setStartTimestampUs: 0
02-28 11:57:52.070 6420-6965/com.myapp.broadcast I/MPEG4Writer: Earliest track starting time: 0
02-28 11:57:52.083 6420-6966/com.myapp.broadcast I/MPEG4Writer: setStartTimestampUs: 0
02-28 11:58:02.617 6420-6475/com.myapp.broadcast W/AndroidMuxer: writeSampleData: All tracks finished! Calling Stop
02-28 11:58:02.617 6420-6475/com.myapp.broadcast W/AndroidMuxer: stop: Calling stop to the muxer
02-28 11:58:02.617 6420-6475/com.myapp.broadcast D/MPEG4Writer: Audio track stopping
02-28 11:58:02.617 6420-6475/com.myapp.broadcast D/MPEG4Writer: Audio track source stopping
02-28 11:58:02.617 6420-6475/com.myapp.broadcast D/MPEG4Writer: Audio track source stopped
02-28 11:58:02.617 6420-6965/com.myapp.broadcast I/MPEG4Writer: Received total/0-length (452/0) buffers and encoded 452 frames. - Audio
02-28 11:58:02.617 6420-6965/com.myapp.broadcast I/MPEG4Writer: Audio track drift time: 0 us
02-28 11:58:02.617 6420-6475/com.myapp.broadcast D/MPEG4Writer: Audio track stopped
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Video track stopping
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Video track source stopping
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Video track source stopped
02-28 11:58:02.618 6420-6966/com.myapp.broadcast I/MPEG4Writer: Received total/0-length (315/0) buffers and encoded 315 frames. - Video
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Video track stopped
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Duration from tracks range is [10143021, 10495420] us
02-28 11:58:02.618 6420-6475/com.myapp.broadcast D/MPEG4Writer: Stopping writer thread
02-28 11:58:02.620 6420-6964/com.myapp.broadcast D/MPEG4Writer: 0 chunks are written in the last batch
02-28 11:58:02.620 6420-6475/com.myapp.broadcast D/MPEG4Writer: Writer thread stopped
02-28 11:58:02.621 6420-6475/com.myapp.broadcast I/MPEG4Writer: The mp4 file will not be streamable.
02-28 11:58:02.621 6420-6475/com.myapp.broadcast W/AndroidMuxer: stop: muxer stopped!

Не то, чтобы видео, сохраненное локально, было не в порядке, но прямая трансляция того же материала (полученного в результате того же процесса кодирования) в порядке, поэтому проблема должна быть в самом мультиплексоре (или, точнее, в том, как я его обрабатываю). ).

Это мой Muxer Wrapper (вдохновленный примерами медиакодеков от bigflake):

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * @hide
 */
public class AndroidMuxer extends Muxer {
    private static final String TAG = "AndroidMuxer";
    private static final boolean VERBOSE = false;

    private MediaMuxer mMuxer;
    private boolean mStarted = false;
    private boolean mStopped = false;
    private boolean mReadyToStart;
    private MediaFormat mVideoTrackFormat;
    private MediaFormat mAudioTrackFormat;

    private long mStartTimeUs = 0;
    @Override
    public void reset() {
        Log.w(TAG, "reset: Reset is called on LOCAL MUXER!");

            mStarted = false;
            mReadyToStart = false;
            mNumTracks = 0;
            mNumTracksFinished = 0;
            Log.w(TAG, "reset: Reset worked on the Muxer");

    }

    /**
     * This will only initialize the fields, we only want the real muxer to be initialized
     * when we go live.
     * @param outputFile outputPath of the file
     * @param format Format of the muxer (will always be MPEG4 TBH)
     */
    public AndroidMuxer(String outputFile, FORMAT format) {
        super(outputFile, format);
        mStarted = false;
    }

    @Override
    public void init() {
        if(!mReadyToStart) {
            return;
        }

        if(mStarted){
            Log.i(TAG, "init: Muxer already created! Skipping!");
            return;
        }


        try {
            switch (mFormat) {
                case MPEG4:
                    mMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                    int audioTrack = mMuxer.addTrack(mAudioTrackFormat);
                    Log.i(TAG, "init: Added audio track widh id " + audioTrack);
                    mAudioTrackIndex = audioTrack;

                    int videoTrack = mMuxer.addTrack(mVideoTrackFormat);
                    Log.i(TAG, "init: Added video track widh id " + videoTrack);
                    mVideoTrackIndex = videoTrack;
                    Log.w(TAG, "init: Muxer was successfully created! Starting to record");
                    start();
                    break;
                default:
                    throw new IllegalArgumentException("Unrecognized format!");
            }
        } catch (IOException e) {
            throw new RuntimeException("MediaMuxer creation failed", e);
        }
    }

    public static AndroidMuxer create(String outputFile, FORMAT format) {
        return new AndroidMuxer(outputFile, format);
    }

    @Override
    public int addTrack(MediaFormat trackFormat, AndroidEncoder.TrackType trackType) {
        Log.w(TAG, "addTrack: Requested adding of track to class " + getClass().getSimpleName() + " of type " + trackType);
        //Super method just keeps track of amount of tracks added
        super.addTrack(trackFormat, trackType);
        Log.w(TAG, "addTrack: Adding track for " + trackFormat.getString(MediaFormat.KEY_MIME));
        if (mStarted)
            throw new RuntimeException("format changed twice");

        //What we do is store the media info for later use once the muxer is created.
        //This is done to prevent file creation until the event goes live.

        if(trackType == AndroidEncoder.TrackType.VIDEO){
            mVideoTrackFormat = trackFormat;
        }
        else if(trackType == AndroidEncoder.TrackType.AUDIO){
            mAudioTrackFormat = trackFormat;
        }



        if (allTracksAdded()) {
            Log.w(TAG, "addTrack: All tracks added, marking Local muxer as ready!");
            markReadyToStart();
        }
        Log.w(TAG, "addTrack: Track index to class " + getClass().getSimpleName() + " of type " + trackType + ", track saved for future use!");
        //Return dummy
        return 1;
    }


    /**
     *
     * Marking the Muxer as ready to be started but not yet!
     *
     */
    private void markReadyToStart() {
        mReadyToStart = true;
    }



    private void start() {
        if(mStarted){
            Log.i(TAG, "start: Skipped start due to muxer already started");
        }
        mStartTimeUs = 0;
        mMuxer.start();
        Log.w(TAG, "start: Muxer started!");
        mStarted = true;
    }

    protected void stop() {
        if(mStarted) {
            Log.w(TAG, "stop: Calling stop to the muxer");
            mMuxer.stop();
            Log.w(TAG, "stop: muxer stopped!");
            mStarted = false;
            mStopped = true;
            mReadyToStart = false;
        }
    }

    @Override
    public void release() {
        super.release();
        if(mStopped) {
            mMuxer.release();
            mStopped = false;
            mStarted = false;
        }
    }

    @Override
    public boolean isStarted() {
        return mStarted;
    }

    @Override
    public void writeSampleData(MediaCodec encoder, int trackIndex, int bufferIndex, ByteBuffer encodedData, MediaCodec.BufferInfo bufferInfo) {
        if(!mStarted) {

            return;
        }

        super.writeSampleData(encoder, trackIndex, bufferIndex, encodedData, bufferInfo);
        if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
            // MediaMuxer gets the codec config info via the addTrack command
            if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
            return;
        }

        if (bufferInfo.size == 0) {
            if (VERBOSE) Log.d(TAG, "ignoring zero size buffer");
            return;
        }

        if (!mStarted) {
            Log.w(TAG, "writeSampleData called before muxer started. Ignoring packet. Track index: " + trackIndex + " tracks added: " + mNumTracks);
//            encoder.releaseOutputBuffer(bufferIndex, false);
            return;
        }

//        bufferInfo.presentationTimeUs = getNextRelativePts(bufferInfo.presentationTimeUs, trackIndex);


       if(mStartTimeUs == 0) {
              mStartTimeUs = bufferInfo.presentationTimeUs;
             }
        bufferInfo.presentationTimeUs -= mStartTimeUs;
        if(bufferInfo.presentationTimeUs < 0) {
             bufferInfo.presentationTimeUs = 0;
        }
        bufferInfo.presentationTimeUs = getSafePts(bufferInfo.presentationTimeUs, trackIndex);


        mMuxer.writeSampleData(trackIndex, encodedData, bufferInfo);

//        encoder.releaseOutputBuffer(bufferIndex, false);

        if (allTracksFinished()) {
            Log.w(TAG, "writeSampleData: All tracks finished! Calling Stop");
            stop();
        }
    }

    @Override
    public void forceStop() {
        Log.w(TAG, "forceStop: ForceStop called!");
        stop();
    }
}

Обратите внимание, что мультиплексор RTMP и локальный мультиплексор могут не начать работать одновременно, поэтому я сохраняю информацию о медиа, а затем использую ее только тогда, когда пользователь запрашивает запуск мультиплексора.


person Gabriel Sanmartin    schedule 28.02.2017    source источник


Ответы (1)


Если локальный мультиплексор еще не готов и вы пропускаете запись пакетов, вы можете пропустить запись ключевого кадра. Более поздние видеокадры, которые не являются ключевыми кадрами, не могут быть декодированы, пока вы не достигнете следующего ключевого кадра (и кто знает, может ли видеопроигрыватель запутаться, если файл начинается с не ключевого кадра?). Если у вас большой интервал между ключевыми кадрами, вы можете вообще не получить ключевой кадр в течение некоторого разумного времени. (Хотя, если вы также транслируете это через RTMP, у вас, вероятно, регулярно есть ключевые кадры.)

Я бы посоветовал либо сохранить все пакеты, созданные до того, как локальный мультиплексор будет готов, и записать их в локальный мультиплексор, как только он будет готов, либо продолжать пропускать пакеты, пока не будет достигнут следующий ключевой кадр.

person mstorsjo    schedule 28.02.2017
comment
Не могу этого сделать, мое приложение разработано таким образом, что вы можете начать прямую трансляцию, а затем выбрать любой момент, чтобы начать запись локально. Что я могу сделать, так это начать кормить локальный кодировщик только тогда, когда я получаю ключевой кадр. Однако я не уверен, что это проблема, потому что если бы это было проблемой, то а) я бы почти никогда не получил ключевой кадр в качестве первого кадра, и поэтому я почти никогда не смог бы иметь правильную запись и б) я думаю, что если бы это была проблема, тогда я могу получить 2 черные секунды, а затем видео возобновится должным образом, как только я получу ключевой кадр (у меня GOP установлено каждые 2 секунды). - person Gabriel Sanmartin; 28.02.2017
comment
Тем не менее, я попробую этот подход. Это тоже не объясняет, я думаю, почему у меня иногда получается сначала звук, а потом все видео на каких-то локально записанных файлах. - person Gabriel Sanmartin; 28.02.2017