проблема синхронизации аудио и видео дорожек при использовании медиакодека и медиамуксера для файлов mp4

Я хотел бы создать файл mp4 путем мультиплексирования звука с микрофона (перезаписать didGetAudioData) и видео с камеры (перезаписать onpreviewframe). Однако я столкнулся с проблемой синхронизации звука и видео, видео будет отображаться быстрее, чем звук. Я задавался вопросом, связана ли проблема с несовместимыми конфигурациями или PresentationTimeUs, может ли кто-нибудь подсказать мне, как решить эту проблему. Ниже был мой софт.

Конфигурация видео

formatVideo = MediaFormat.createVideoFormat(MIME_TYPE_VIDEO, 640, 360);
formatVideo.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
formatVideo.setInteger(MediaFormat.KEY_BIT_RATE, 2000000);
formatVideo.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
formatVideo.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);

получил видеопрезентацию PTS, как показано ниже,

if(generateIndex == 0) {
    videoAbsolutePtsUs = 132;
    StartVideoAbsolutePtsUs = System.nanoTime() / 1000L;
}else {
    CurrentVideoAbsolutePtsUs = System.nanoTime() / 1000L;
    videoAbsolutePtsUs =132+ CurrentVideoAbsolutePtsUs-StartVideoAbsolutePtsUs;
}
generateIndex++;

аудио конфигурация

format = MediaFormat.createAudioFormat(MIME_TYPE, 48000/*sample rate*/, AudioFormat.CHANNEL_IN_MONO /*Channel config*/);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE,48000);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT,1);
format.setInteger(MediaFormat.KEY_BIT_RATE,64000);

получил звуковую презентацию PTS, как показано ниже,

if(generateIndex == 0) {
   audioAbsolutePtsUs = 132;
   StartAudioAbsolutePtsUs = System.nanoTime() / 1000L;
}else {
   CurrentAudioAbsolutePtsUs = System.nanoTime() / 1000L;
   audioAbsolutePtsUs =CurrentAudioAbsolutePtsUs - StartAudioAbsolutePtsUs;
}

generateIndex++;
audioAbsolutePtsUs = getJitterFreePTS(audioAbsolutePtsUs, audioInputLength / 2);

long startPTS = 0;
long totalSamplesNum = 0;
private long getJitterFreePTS(long bufferPts, long bufferSamplesNum) {
    long correctedPts = 0;
    long bufferDuration = (1000000 * bufferSamplesNum) / 48000;
    bufferPts -= bufferDuration; // accounts for the delay of acquiring the audio buffer
    if (totalSamplesNum == 0) {
        // reset
        startPTS = bufferPts;
        totalSamplesNum = 0;
    }
    correctedPts = startPTS +  (1000000 * totalSamplesNum) / 48000;
    if(bufferPts - correctedPts >= 2*bufferDuration) {
        // reset
        startPTS = bufferPts;
        totalSamplesNum = 0;
        correctedPts = startPTS;
    }
    totalSamplesNum += bufferSamplesNum;
    return correctedPts;
}

Была ли моя проблема вызвана применением функции джиттера только для звука? Если да, как я могу применить функцию дрожания для видео? Я также пытался найти правильную аудио- и видеопрезентацию PTS по https://android.googlesource.com/platform/cts/+/jb-mr2-release/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java. Но encodedecodeTest предоставил только видео PTS. По этой причине в моей реализации использовалось системное нановремя как для аудио, так и для видео. Если я хочу использовать видеопрезентацию PTS в тесте кодирования-декодирования, как создать совместимую аудиопрезентацию PTS? Спасибо за помощь!

ниже показано, как я ставлю кадр yuv в видео медиакодек для справки. Для звуковой части он идентичен, за исключением разного представления PTS.

int videoInputBufferIndex;
int videoInputLength;
long videoAbsolutePtsUs;
long StartVideoAbsolutePtsUs, CurrentVideoAbsolutePtsUs;

int put_v =0;
int get_v =0;
int generateIndex = 0;

public void setByteBufferVideo(byte[] buffer, boolean isUsingFrontCamera, boolean Input_endOfStream){
    if(Build.VERSION.SDK_INT >=18){
        try{

            endOfStream = Input_endOfStream;
            if(!Input_endOfStream){
            ByteBuffer[] inputBuffers = mVideoCodec.getInputBuffers();
            videoInputBufferIndex = mVideoCodec.dequeueInputBuffer(-1);

                if (VERBOSE) {
                    Log.w(TAG,"[put_v]:"+(put_v)+"; videoInputBufferIndex = "+videoInputBufferIndex+"; endOfStream = "+endOfStream);
                }

                if(videoInputBufferIndex>=0) {
                    ByteBuffer inputBuffer = inputBuffers[videoInputBufferIndex];
                    inputBuffer.clear();

                    inputBuffer.put(mNV21Convertor.convert(buffer));
                    videoInputLength = buffer.length;

                    if(generateIndex == 0) {
                        videoAbsolutePtsUs = 132;
                        StartVideoAbsolutePtsUs = System.nanoTime() / 1000L;
                    }else {
                        CurrentVideoAbsolutePtsUs = System.nanoTime() / 1000L;
                        videoAbsolutePtsUs =132+ CurrentVideoAbsolutePtsUs - StartVideoAbsolutePtsUs;
                    }

                    generateIndex++;

                    if (VERBOSE) {
                        Log.w(TAG, "[put_v]:"+(put_v)+"; videoAbsolutePtsUs = " + videoAbsolutePtsUs + "; CurrentVideoAbsolutePtsUs = "+CurrentVideoAbsolutePtsUs);
                    }

                    if (videoInputLength == AudioRecord.ERROR_INVALID_OPERATION) {
                        Log.w(TAG, "[put_v]ERROR_INVALID_OPERATION");
                    } else if (videoInputLength == AudioRecord.ERROR_BAD_VALUE) {
                        Log.w(TAG, "[put_v]ERROR_ERROR_BAD_VALUE");
                    }
                    if (endOfStream) {
                        Log.w(TAG, "[put_v]:"+(put_v++)+"; [get] receive endOfStream");
                        mVideoCodec.queueInputBuffer(videoInputBufferIndex, 0, videoInputLength, videoAbsolutePtsUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    } else {
                        Log.w(TAG, "[put_v]:"+(put_v++)+"; receive videoInputLength :" + videoInputLength);
                        mVideoCodec.queueInputBuffer(videoInputBufferIndex, 0, videoInputLength, videoAbsolutePtsUs, 0);
                    }
                }
            }
        }catch (Exception x) {
            x.printStackTrace();
        }
    }
}

person Yu-Cheng Fan    schedule 21.03.2016    source источник


Ответы (1)


Я решил эту проблему в своем приложении, установив PTS всех видео- и аудиокадров в соответствии с общими «синхронизирующими часами» (обратите внимание, что синхронизация также означает потокобезопасность), которые запускаются, когда первый видеокадр (имеющий PTS 0 сам по себе). Таким образом, если аудиозапись начинается раньше, чем видео, аудиоданные отбрасываются (не попадают в кодировщик) до тех пор, пока не начнется видео, а если она начинается позже, то первая звуковая PTS будет относиться к началу всего видео.

Конечно, вы можете позволить звуку запускаться первым, но игроки все равно будут пропускать или ждать первого видеокадра. Также будьте осторожны, что закодированные аудиокадры будут поступать «не по порядку», и MediaMuxer рано или поздно выйдет из строя с ошибкой. Мое решение состояло в том, чтобы поставить их в очередь следующим образом: сортировать их по pts, когда приходит новый, затем записывать все, что старше 500 мс (относительно самого нового) в MediaMuxer, но только те, у которых PTS выше, чем у последнего письменная рамка. В идеале это означает, что данные плавно записываются в MediaMuxer с задержкой в ​​500 мс. В худшем случае вы потеряете несколько звуковых кадров.

person Adrian Crețu    schedule 22.03.2016
comment
Спасибо Адриан, можно вопрос? Я обнаружил, что если я удаляю функцию устранения джиттера звука (getJitterFreePTS), аудио dequeueOutputBuffer останавливается после отправки нескольких аудиоданных в медиакодек без явных ошибок. Есть ли у вас идеи о любой возможной причине проблемы? - person Yu-Cheng Fan; 22.03.2016
comment
Проверьте PTS, он должен монотонно возрастать, но судя по вашему коду, вы начинаете со 133, и, скорее всего, вы получите что-то меньшее, чем во втором примере. Использование часов синхронизации решает эту проблему, и, кстати, я не уверен, что вам нужна функция джиттера, вы получаете звук блоками. Кроме того, не гарантируется, что 48 кГц будет работать на всех устройствах, единственная гарантированная частота дискретизации в Android — 44100. - person Adrian Crețu; 22.03.2016
comment
@AdrianCrețu У меня есть механизм записи, который записывает как видео, так и аудио, и где оба потока получают временную метку PTS от System.nano(), к сожалению, это вызывает дрожание звука и приводит к рассинхронизации звука и небольшой задержке в финальном видео. прочитал ваше решение, но не смог заставить его работать, не могли бы вы привести пример кода для того, как вы реализовали свою систему PTS? - person Steve; 10.03.2017
comment
@Steve Это довольно просто, я не могу поделиться точным кодом. Убедитесь, что вы инициализируете часы сразу после получения первого ключевого кадра видео, который вы будете использовать. Все, что происходит до этого, отбрасывается на стороне звука. Конечно, если вы выполняете сложные блокирующие операции с видео- или аудиопотоком (например, кодирование аудио), то довольно скоро все выйдет из синхронизации, поэтому убедитесь, что у них есть место для дыхания. - person Adrian Crețu; 11.03.2017
comment
Не могли бы вы предоставить код? Спасибо - person Pablo Martinez; 09.01.2021