Первый аудио CMSampleBuffer потерян при чтении файла mp4 с помощью AVAssetReader

Я использую AVAssetWriter для записи аудио CMSampleBuffer в файл mp4, но когда я позже прочитал этот файл с помощью AVAssetReader, кажется, что отсутствует начальный фрагмент данных.

Вот отладочное описание первого CMSampleBuffer, переданного в метод добавления ввода записи (обратите внимание на добавление длительности заполнения 1024/44_100):

CMSampleBuffer 0x102ea5b60 retainCount: 7 allocator: 0x1c061f840
            invalid = NO
            dataReady = YES
            makeDataReadyCallback = 0x0
            makeDataReadyRefcon = 0x0
            buffer-level attachments:
                TrimDurationAtStart    = {
            epoch = 0;
            flags = 1;
            timescale = 44100;
            value = 1024;
        }
            formatDescription = <CMAudioFormatDescription 0x281fd9720 [0x1c061f840]> {
            mediaType:'soun' 
            mediaSubType:'aac ' 
            mediaSpecific: {
                ASBD: {
                    mSampleRate: 44100.000000 
                    mFormatID: 'aac ' 
                    mFormatFlags: 0x2 
                    mBytesPerPacket: 0 
                    mFramesPerPacket: 1024 
                    mBytesPerFrame: 0 
                    mChannelsPerFrame: 2 
                    mBitsPerChannel: 0  } 
                cookie: {<CFData 0x2805f50a0 [0x1c061f840]>{length = 39, capacity = 39, bytes = 0x03808080220000000480808014401400 ... 1210068080800102}} 
                ACL: {(null)}
                FormatList Array: {
                    Index: 0 
                    ChannelLayoutTag: 0x650002 
                    ASBD: {
                    mSampleRate: 44100.000000 
                    mFormatID: 'aac ' 
                    mFormatFlags: 0x0 
                    mBytesPerPacket: 0 
                    mFramesPerPacket: 1024 
                    mBytesPerFrame: 0 
                    mChannelsPerFrame: 2 
                    mBitsPerChannel: 0  }} 
            } 
            extensions: {(null)}
        }
            sbufToTrackReadiness = 0x0
            numSamples = 1
            outputPTS = {6683542167/44100 = 151554.244, rounded}(based on cachedOutputPresentationTimeStamp)
            sampleTimingArray[1] = {
                {PTS = {6683541143/44100 = 151554.221, rounded}, DTS = {6683541143/44100 = 151554.221, rounded}, duration = {1024/44100 = 0.023}},
            }
            sampleSizeArray[1] = {
                sampleSize = 163,
            }
            dataBuffer = 0x281cc7a80

Вот отладочное описание второго CMSampleBuffer (обратите внимание на добавление длительности заполнения 1088/44_100, которое в сочетании с предыдущей продолжительностью обрезки дает стандартное значение 2112):

CMSampleBuffer 0x102e584f0 retainCount: 7 allocator: 0x1c061f840
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        TrimDurationAtStart    = {
    epoch = 0;
    flags = 1;
    timescale = 44100;
    value = 1088;
}
    formatDescription = <CMAudioFormatDescription 0x281fd9720 [0x1c061f840]> {
    mediaType:'soun' 
    mediaSubType:'aac ' 
    mediaSpecific: {
        ASBD: {
            mSampleRate: 44100.000000 
            mFormatID: 'aac ' 
            mFormatFlags: 0x2 
            mBytesPerPacket: 0 
            mFramesPerPacket: 1024 
            mBytesPerFrame: 0 
            mChannelsPerFrame: 2 
            mBitsPerChannel: 0  } 
        cookie: {<CFData 0x2805f50a0 [0x1c061f840]>{length = 39, capacity = 39, bytes = 0x03808080220000000480808014401400 ... 1210068080800102}} 
        ACL: {(null)}
        FormatList Array: {
            Index: 0 
            ChannelLayoutTag: 0x650002 
            ASBD: {
            mSampleRate: 44100.000000 
            mFormatID: 'aac ' 
            mFormatFlags: 0x0 
            mBytesPerPacket: 0 
            mFramesPerPacket: 1024 
            mBytesPerFrame: 0 
            mChannelsPerFrame: 2 
            mBitsPerChannel: 0  }} 
    } 
    extensions: {(null)}
}
    sbufToTrackReadiness = 0x0
    numSamples = 1
    outputPTS = {6683543255/44100 = 151554.269, rounded}(based on cachedOutputPresentationTimeStamp)
    sampleTimingArray[1] = {
        {PTS = {6683542167/44100 = 151554.244, rounded}, DTS = {6683542167/44100 = 151554.244, rounded}, duration = {1024/44100 = 0.023}},
    }
    sampleSizeArray[1] = {
        sampleSize = 179,
    }
    dataBuffer = 0x281cc4750

Теперь, когда я читаю звуковую дорожку с помощью AVAssetReader, первый CMSampleBuffer, который я получаю:

CMSampleBuffer 0x102ed7b20 retainCount: 7 allocator: 0x1c061f840
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        EmptyMedia(P) = true
    formatDescription = (null)
    sbufToTrackReadiness = 0x0
    numSamples = 0
    outputPTS = {0/1 = 0.000}(based on outputPresentationTimeStamp)
    sampleTimingArray[1] = {
        {PTS = {0/1 = 0.000}, DTS = {INVALID}, duration = {0/1 = 0.000}},
    }
    dataBuffer = 0x0

а следующий содержит вводную информацию 1088/44_100:

CMSampleBuffer 0x10318bc00 retainCount: 7 allocator: 0x1c061f840
    invalid = NO
    dataReady = YES
    makeDataReadyCallback = 0x0
    makeDataReadyRefcon = 0x0
    buffer-level attachments:
        FillDiscontinuitiesWithSilence(P) = true
        GradualDecoderRefresh(P) = 1
        TrimDurationAtStart(P) = {
    epoch = 0;
    flags = 1;
    timescale = 44100;
    value = 1088;
}
        IsGradualDecoderRefreshAuthoritative(P) = false
    formatDescription = <CMAudioFormatDescription 0x281fdcaa0 [0x1c061f840]> {
    mediaType:'soun' 
    mediaSubType:'aac ' 
    mediaSpecific: {
        ASBD: {
            mSampleRate: 44100.000000 
            mFormatID: 'aac ' 
            mFormatFlags: 0x0 
            mBytesPerPacket: 0 
            mFramesPerPacket: 1024 
            mBytesPerFrame: 0 
            mChannelsPerFrame: 2 
            mBitsPerChannel: 0  } 
        cookie: {<CFData 0x2805f3800 [0x1c061f840]>{length = 39, capacity = 39, bytes = 0x03808080220000000480808014401400 ... 1210068080800102}} 
        ACL: {Stereo (L R)}
        FormatList Array: {
            Index: 0 
            ChannelLayoutTag: 0x650002 
            ASBD: {
            mSampleRate: 44100.000000 
            mFormatID: 'aac ' 
            mFormatFlags: 0x0 
            mBytesPerPacket: 0 
            mFramesPerPacket: 1024 
            mBytesPerFrame: 0 
            mChannelsPerFrame: 2 
            mBitsPerChannel: 0  }} 
    } 
    extensions: {{
    VerbatimISOSampleEntry = {length = 87, bytes = 0x00000057 6d703461 00000000 00000001 ... 12100680 80800102 };
}}
}
    sbufToTrackReadiness = 0x0
    numSamples = 43
    outputPTS = {83/600 = 0.138}(based on outputPresentationTimeStamp)
    sampleTimingArray[1] = {
        {PTS = {1024/44100 = 0.023}, DTS = {1024/44100 = 0.023}, duration = {1024/44100 = 0.023}},
    }
    sampleSizeArray[43] = {
        sampleSize = 179,
        sampleSize = 173,
        sampleSize = 178,
        sampleSize = 172,
        sampleSize = 172,
        sampleSize = 159,
        sampleSize = 180,
        sampleSize = 200,
        sampleSize = 187,
        sampleSize = 189,
        sampleSize = 206,
        sampleSize = 192,
        sampleSize = 195,
        sampleSize = 186,
        sampleSize = 183,
        sampleSize = 189,
        sampleSize = 211,
        sampleSize = 198,
        sampleSize = 204,
        sampleSize = 211,
        sampleSize = 204,
        sampleSize = 202,
        sampleSize = 218,
        sampleSize = 210,
        sampleSize = 206,
        sampleSize = 207,
        sampleSize = 221,
        sampleSize = 219,
        sampleSize = 236,
        sampleSize = 219,
        sampleSize = 227,
        sampleSize = 225,
        sampleSize = 225,
        sampleSize = 229,
        sampleSize = 225,
        sampleSize = 236,
        sampleSize = 233,
        sampleSize = 231,
        sampleSize = 249,
        sampleSize = 234,
        sampleSize = 250,
        sampleSize = 249,
        sampleSize = 259,
    }
    dataBuffer = 0x281cde370

Входной метод добавления продолжает возвращать true, что в принципе означает, что все буферы выборки добавлены, но считыватель по какой-то причине пропускает первый блок данных. Есть ли что-то, что я делаю неправильно здесь?

Я использую следующий код для чтения файла:

let asset = AVAsset(url: fileURL)
guard let assetReader = try? AVAssetReader(asset: asset) else {
    return
}

asset.loadValuesAsynchronously(forKeys: ["tracks"]) { in
    guard let audioTrack = asset.tracks(withMediaType: .audio).first else { return }
    let audioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
    assetReader.startReading()

    while assetReader.status == .reading {
        if let sampleBuffer = audioOutput.copyNextSampleBuffer() {
            // do something
        }
    }
}

person Grzegorz Aperliński    schedule 21.02.2020    source источник


Ответы (1)


Сначала немного педантизма: вы потеряли не свой первый буфер сэмплов, а первый пакет в вашем первом буфере сэмплов.

Поведение AVAssetReader с nil outputSettings при чтении данных пакета AAC изменилось в iOS 13 и macOS 10.15 (Catalina).

Раньше вы получали первый пакет AAC, отметку времени представления этого пакета (ноль) и вложение обрезки, в котором указывалось, что вы отбрасываете обычные первые 2112 кадров декодированного звука.

Теперь [iOS 13, macOS 10.15] AVAssetReader, кажется, отбрасывает первый пакет, оставляя вам второй пакет, временная метка представления которого равна 1024, и вам нужно только отбросить 2112 - 1024 = 1088 декодированных кадров.

Что-то, что может быть не сразу очевидно в приведенных выше ситуациях, заключается в том, что AVAssetReader говорит о ДВУХ временных шкалах, а не об одной. Временные метки пакетов относятся к одной, неусеченной временной шкале, а инструкция обрезки подразумевает существование другой: неусеченной временной шкалы.

Преобразование необрезанных временных меток в усеченные очень просто, обычно это trimmed = untrimmed - 2112.

Так является ли новое поведение ошибкой? Тот факт, что если вы декодируете в LPCM и правильно следуете инструкциям по обрезке, вы все равно должны получить тот же звук, наводит меня на мысль, что изменение было преднамеренным (NB: я еще лично не подтвердил, что образцы LPCM одинаковы).

Однако в документации сказано:

Значение nil для outputSettings настраивает вывод на продажу семплов в их исходном формате, сохраненном указанной дорожкой.

Я не думаю, что вы можете одновременно отбрасывать пакеты [даже первый, который в основном является константой] и заявлять, что продаёте образцы в их «исходном формате», поэтому с этой точки зрения я думаю, что изменение имеет багоподобный характер. качество.

Я также думаю, что это неудачное изменение, поскольку раньше я считал nil outputSettings AVAssetReader своего рода «сырым» режимом, но теперь он предполагает, что ваш единственный вариант использования — декодирование в LPCM.

Есть только одна вещь, которая может понизить «неудачный» до «серьезной ошибки», и это если этот новый подход «давайте притворимся, что первый пакет AAC не существует» распространяется на файлы, созданные с помощью AVAssetWriter, потому что это нарушит совместимость с кодом, отличным от AVAssetReader. , где количество кадров для обрезки застыло до постоянных 2112 кадров. Я тоже лично этого не подтверждал. У вас есть файл, созданный с приведенными выше образцами буферов, которым вы можете поделиться?

p.s. Я не думаю, что ваши входные буферы выборки здесь актуальны, я думаю, вы потеряете первое чтение пакета из любого файла AAC. Однако ваши входные семплированные буферы кажутся немного необычными в том смысле, что они имеют временные метки в стиле hosttime [capture session?], но являются AAC и имеют только один пакет на семпловый буфер, что не очень много и похоже на большие накладные расходы для 23 мс аудио. Вы их сами создаете в цепочке AVCaptureSession -> AVAudioConverter?

person Rhythmic Fistman    schedule 28.05.2020
comment
Я только что заметил ваш ответ и очень ценю, что вы нашли время, чтобы объяснить и уточнить это для меня. Я не эксперт по преобразованию и обработке звука, поэтому иногда мой жаргон определенно может быть неправильным. Однако я нашел решение своей проблемы и поделился своими выводами в этом сообщении в блоге: -core-media-api-c5b2bf0e62a1" rel="nofollow noreferrer">medium.com/fandom-engineering/ Дайте мне знать, если у вас есть какие-либо отзывы :) - person Grzegorz Aperliński; 16.06.2020
comment
Кроме того, что касается сообщения в блоге - я не говорю, что это правильный способ исправить это, но это действительно устраняет мою проблему, так что... - person Grzegorz Aperliński; 16.06.2020
comment
Я думаю, что ваше сообщение в блоге и решение верны! Я не думаю, что дал ответ, но я много говорил. Ваш вопрос был Я делаю что-то неправильно здесь? и ответ, я думаю, нет. - person Rhythmic Fistman; 01.08.2020