Захват / сегментирование видео на iOS и повторное присоединение через HLS приводит к пропаданию звука

Я пытаюсь записать видео на iPhone 5 для загрузки в реальном времени и потоковой передачи HLS. Я нахожусь на этапе создания видео на устройстве (еще не загружающего на сервер). Как и предполагают эти ссылки на SO, я собрал код, который отключает AssetWriters каждые пять секунд.

Прямо сейчас во время разработки я просто сохраняю файлы на устройство локально и вытаскиваю их через XCode Organizer. Затем я запускаю Mediafilesegmenter от Apple, чтобы просто преобразовать их в MPEG2-TS (они уже менее 10 секунд, поэтому фактического сегментирования не происходит - я предполагаю, что они просто конвертируются в TS). Я создаю m3u8, редактируя вместе различные индексные файлы, созданные во время этого процесса (в настоящее время также вручную).

Когда я помещаю ресурсы на сервер для тестирования, они в основном транслируются правильно, но я могу сказать, когда происходит переключение сегмента, потому что звук на короткое время падает (возможно, видео тоже, но я не могу сказать наверняка - выглядит нормально ). Очевидно, этого не происходит для типичных потоков HLS, сегментированных из одного входного файла. Я не понимаю, что вызывает это.

Вы можете открыть мой поток HLS на своем iPhone здесь (вы можете услышать пропадание звука через 5 секунд и снова около 10)

Может ли что-то происходить в процессе моего создания (на устройстве или при постобработке), что вызывает кратковременные пропадания звука? Я не думаю, что отбрасываю какой-либо sampleBuffer во время переключения AssetWriter (см. Код).

- (void)writeSampleBuffer:(CMSampleBufferRef)sampleBuffer ofType:(NSString *)mediaType
{
    if (!self.isStarted) {
        return;
    }

    @synchronized(self) {

        if (mediaType == AVMediaTypeVideo && !assetWriterVideoIn) {
            videoFormat = CMSampleBufferGetFormatDescription(sampleBuffer);
            CFRetain(videoFormat);
            assetWriterVideoIn = [self addAssetWriterVideoInput:assetWriter withFormatDesc:videoFormat];
            [tracks addObject:AVMediaTypeVideo];
            return;
        }

        if (mediaType == AVMediaTypeAudio && !assetWriterAudioIn) {
            audioFormat = CMSampleBufferGetFormatDescription(sampleBuffer);
            CFRetain(audioFormat);
            assetWriterAudioIn = [self addAssetWriterAudioInput:assetWriter withFormatDesc:audioFormat];
            [tracks addObject:AVMediaTypeAudio];
            return;
        }

        if (assetWriterAudioIn && assetWriterVideoIn) {
            recording = YES;
            if (assetWriter.status == AVAssetWriterStatusUnknown) {
                if ([assetWriter startWriting]) {
                    [assetWriter startSessionAtSourceTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
                    if (segmentationTimer) {
                        [self setupQueuedAssetWriter];
                        [self startSegmentationTimer];
                    }
                } else {
                    [self showError:[assetWriter error]];
                }
            }

            if (assetWriter.status == AVAssetWriterStatusWriting) {
                if (mediaType == AVMediaTypeVideo) {
                    if (assetWriterVideoIn.readyForMoreMediaData) {
                        if (![assetWriterVideoIn appendSampleBuffer:sampleBuffer]) {
                            [self showError:[assetWriter error]];
                        }
                    }
                }
                else if (mediaType == AVMediaTypeAudio) {
                    if (assetWriterAudioIn.readyForMoreMediaData) {
                        if (![assetWriterAudioIn appendSampleBuffer:sampleBuffer]) {
                            [self showError:[assetWriter error]];
                        }
                    }
                }
            }
        }
    }
}

- (void)setupQueuedAssetWriter
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"Setting up queued asset writer...");
        queuedFileURL = [self nextFileURL];
        queuedAssetWriter = [[AVAssetWriter alloc] initWithURL:queuedFileURL fileType:AVFileTypeMPEG4 error:nil];
        if ([tracks objectAtIndex:0] == AVMediaTypeVideo) {
            queuedAssetWriterVideoIn = [self addAssetWriterVideoInput:queuedAssetWriter withFormatDesc:videoFormat];
            queuedAssetWriterAudioIn = [self addAssetWriterAudioInput:queuedAssetWriter withFormatDesc:audioFormat];
        } else {
            queuedAssetWriterAudioIn = [self addAssetWriterAudioInput:queuedAssetWriter withFormatDesc:audioFormat];
            queuedAssetWriterVideoIn = [self addAssetWriterVideoInput:queuedAssetWriter withFormatDesc:videoFormat];
        }
    });
}

- (void)doSegmentation
{
    NSLog(@"Segmenting...");
    AVAssetWriter *writer = assetWriter;
    AVAssetWriterInput *audioIn = assetWriterAudioIn;
    AVAssetWriterInput *videoIn = assetWriterVideoIn;
    NSURL *fileURL = currentFileURL;

    //[avCaptureSession beginConfiguration];
    @synchronized(self) {
        assetWriter = queuedAssetWriter;
        assetWriterAudioIn = queuedAssetWriterAudioIn;
        assetWriterVideoIn = queuedAssetWriterVideoIn;
    }
    //[avCaptureSession commitConfiguration];
    currentFileURL = queuedFileURL;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [audioIn markAsFinished];
        [videoIn markAsFinished];
        [writer finishWritingWithCompletionHandler:^{
            if (writer.status == AVAssetWriterStatusCompleted ) {
                [fileURLs addObject:fileURL];
            } else {
                NSLog(@"...WARNING: could not close segment");
            }
        }];
    });
}

person chickeneye    schedule 09.12.2013    source источник


Ответы (3)


Вы можете попробовать вставить # EXT-X-DISCONTINUITY между каждым сегментом m3u8, но я сомневаюсь, что это сработает. Здесь много чего может пойти не так.

Предполагая, что вы используете семпл звука с частотой 44100 кГц. Каждые 22 микросекунды появляется новый семпл звука. Во время закрытия и повторного открытия файла вы определенно теряете образцы. Если вы объедините окончательную форму волны, она будет воспроизводиться немного быстрее, чем в реальном времени из-за этой потери. На самом деле, это, вероятно, не проблема.

Как сказал @vipw, у вас также будут проблемы с отметкой времени. Каждый раз, когда вы запускаете новый mp4, вы начинаете с нулевой отметки времени. Итак, игрок запутался, потому что метки времени постоянно сбрасываются.

Также это формат транспортного потока. TS инкапсулирует каждый кадр в «потоки». HLS обычно имеет 4 (PAT, PMT, аудио и видео), каждый поток разбивается на 188-байтовые пакеты с 4-байтовым заголовком. Заголовки имеют 4-битный счетчик непрерывности потока, который оборачивается при переполнении. Итак, запуская mediafilesegmenter на каждом mp4, вы прерываете поток в каждом сегменте, сбрасывая счетчик непрерывности обратно на ноль.

Вам нужен инструмент, который будет принимать mp4 и создавать потоковый вывод, который поддерживает / перезаписывает временные метки (PTS, DTS, CTS), а также счетчики непрерывности.

person szatmary    schedule 10.12.2013
comment
Спасибо, да, это начинает иметь смысл. По сути, каждый раз, когда я запускаю новый AVAssetWriter, он ДОЛЖЕН убедиться, что файл полностью самодостаточен и независим. Итак, на ваш взгляд, все сбрасывается. Отслеживание и сброс временных меток имеет смысл, я посмотрю. На самом деле мне даже не нужны теги DISCONTINUITY, похоже, без них он хорошо работает - думаю, мои сегменты почти идеальны, но не совсем. - person chickeneye; 10.12.2013
comment
Не бить дохлую лошадь, но и счетчик непрерывности не игнорировать. Это будет мешать вашим потокам непредсказуемым образом. Синтаксический анализатор / декодер может решить игнорировать пакеты, пока он не вернется в синхронизацию. Статья в Википедии о транспортных потоках (а также о PSI и ES) на удивление хороша. - person szatmary; 10.12.2013

Сдвиг пакетов

У нас возникли проблемы с использованием старых версий фильтра ffmpeg pts для сдвига пакетов. Более поздние версии ffmpeg1 и ffmpeg2 поддерживают временные сдвиги для mpegts.

Вот пример вызова ffmpeg, отметьте -t для продолжительности и -initial_time для сдвига в конце команды (продолжайте прокручивать вправо ...) Вот сегмент с 10-секундным сдвигом

/opt/ffmpeg -i /tmp/cameo/58527/6fc2fa1a7418bf9d4aa90aa384d0eef2244631e8 -threads 0 -ss 10 -i /tmp/cameo/58527/79e684d793e209ebc9b12a5ad82298cb5e94cb54 -codec:v libx264 -pix_fmt yuv420p -preset veryfast -strict -2 -bsf:v h264_mp4toannexb -flags -global_header -crf 28 -profile:v baseline -x264opts level=3:keyint_min=24:keyint=24:scenecut=0 -b:v 100000 -bt 100000 -bufsize 100000 -maxrate 100000 -r 12 -s 320x180 -map 0:0 -map 1:0 -codec:a aac -strict -2 -b:a 64k -ab 64k -ac 2 -ar 44100 -t 9.958333333333334 -segment_time 10.958333333333334 -f segment -initial_offset 10 -segment_format mpegts -y /tmp/cameo/58527/100K%01d.ts -codec:v libx264 -pix_fmt yuv420p -preset veryfast -strict -2 -bsf:v h264_mp4toannexb -flags -global_header -crf 28 -profile:v baseline -x264opts level=3:keyint_min=24:keyint=24:scenecut=0 -b:v 200000 -bt 200000 -bufsize 200000 -maxrate 200000 -r 12 -s 320x180 -map 0:0 -map 1:0 -codec:a aac -strict -2 -b:a 64k -ab 64k -ac 2 -ar 44100 -t 9.958333333333334 -segment_time 10.958333333333334 -f segment -initial_offset 10 -segment_format mpegts -y /tmp/cameo/58527/200K%01d.ts -codec:v libx264 -pix_fmt yuv420p -preset veryfast -strict -2 -bsf:v h264_mp4toannexb -flags -global_header -crf 28 -profile:v baseline -x264opts level=3:keyint_min=24:keyint=24:scenecut=0 -b:v 364000 -bt 364000 -bufsize 364000 -maxrate 364000 -r 24 -s 320x180 -map 0:0 -map 1:0 -codec:a aac -strict -2 -b:a 64k -ab 64k -ac 2 -ar 44100 -t 9.958333333333334 -segment_time 10.958333333333334 -f segment -initial_offset 10 -segment_format mpegts -y /tmp/cameo/58527/364K%01d.ts -codec:v libx264 -pix_fmt yuv420p -preset veryfast -strict -2 -bsf:v h264_mp4toannexb -flags -global_header -crf 28 -profile:v baseline -x264opts level=3:keyint_min=24:keyint=24:scenecut=0 -b:v 664000 -bt 664000 -bufsize 664000 -maxrate 664000 -r 24 -s 480x270 -map 0:0 -map 1:0 -codec:a aac -strict -2 -b:a 64k -ab 64k -ac 2 -ar 44100 -t 9.958333333333334 -segment_time 10.958333333333334 -f segment -initial_offset 10 -segment_format mpegts -y /tmp/cameo/58527/664K%01d.ts -codec:v libx264 -pix_fmt yuv420p -preset veryfast -strict -2 -bsf:v h264_mp4toannexb -flags -global_header -crf 23 -profile:v baseline -x264opts level=3.1:keyint_min=24:keyint=24:scenecut=0 -b:v 1264000 -bt 1264000 -bufsize 1264000 -maxrate 1264000 -r 24 -s 640x360 -map 0:0 -map 1:0 -codec:a aac -strict -2 -b:a 64k -ab 64k -ac 2 -ar 44100 -t 9.958333333333334 -segment_time 10.958333333333334 -f segment -initial_offset 10 -segment_format mpegts -y /tmp/cameo/58527/1264K%01d.ts

Также есть адаптация c ++ segmenter, которую я обновил на github, но она была протестирована только на видео только мпегц. AV по-прежнему вызывает некоторые проблемы (я не был уверен, какой тип пакета следует сместить на новое значение - первое видео или первый аудиопакет, выбрал первый видеопакет). Кроме того, когда вы столкнулись с ним, могут возникнуть проблемы с определенными носителями, как вы отметили в вашей проблеме..

Если бы у меня было больше времени, я бы хотел отладить ваш конкретный случай и улучшить шифтер C ++. Я надеюсь, что приведенный выше пример ffmpeg поможет вам заставить работать ваш пример HTTP-трансляции в прямом эфире, мы справились со своей долей проблем с потоковой передачей. В настоящее время мы работаем над звуковым треском, возникающим из сдвинутых сегментов. Исправление состоит в том, чтобы собрать все исходные медиафайлы перед разделением на сегментированные потоки (что мы можем сделать при финализации видео, но это замедлит нас во время итеративных сборок).

person Mark Essel    schedule 12.12.2013
comment
Вспышка звука - это именно та проблема, которую я пытаюсь решить - как ни странно, приятно слышать, что вы тоже ее испытываете :-). Мне удалось получить немного более плотный поток HLS, используя версию команды ffmpeg, которую вы поделили (мне может не понадобиться использовать переключатель, который вы написали, время начала, длительность, PTS - все выглядит теперь довольно хорошо). Просто получаю крошечное падение звука на переключателях сегмента, и иногда это почти незаметно. Я начинаю задаваться вопросом, смогу ли я вообще от него избавиться - возможно, это просто симптом последовательного кодирования нескольких файлов на iOS. - person chickeneye; 13.12.2013
comment
Я думаю, что это результат кодирования нескольких сегментов по отдельности, вместо кодирования нескольких сегментов из одного источника. Вот как я удалил всплывающее окно, но он все еще находится в стадии разработки (необходимо обновить наш api, чтобы получить все сгенерированные файлы в нашем окончательном коде) - person Mark Essel; 13.12.2013

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

Что может сработать, так это объединить записанные сегменты вместе, чтобы новая часть была отмечена на одной временной шкале. Тогда сегментирование должно работать правильно, а переходы между сегментами должны быть плавными в сгенерированном потоке.

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

person vipw    schedule 09.12.2013
comment
Мне было интересно по этому поводу, но я недостаточно знаю о форматах файлов mp4, чтобы знать, как это подтвердить или исправить. Я могу сказать, что я взял необработанные выходные файлы mp4 и объединил их с помощью ffmpeg, чтобы увидеть, на что был похож результат, но все еще были выпадения. Так что, хотя я пишу файлы непрерывно, кажется, что что-то происходит каждый раз, когда AVAssetWriter начинает новый файл. - person chickeneye; 10.12.2013
comment
У вас ffmpeg переписывал метки времени? - person vipw; 10.12.2013
comment
Я пробовал setpts и asetpts, но, похоже, он не перезаписывает существующие pts, вместо этого похоже, что он добавляет больше. ffmpeg cli - это круто, я не уверен, что делаю правильную команду. В настоящее время я использую: ffmpeg -i input.ts -vf 'setpts = 572250 + PTS' output.ts input.ts имеет 119 пакетов до, а затем 273 пакета после в видеопотоке. Не уверен, что происходит. - person chickeneye; 11.12.2013