在iOS上捕获/分割视频并通过HLS重新加入会导致音频丢失



我正试图在iPhone 5上拍摄视频,以便实时上传和HLS流媒体。我正处于在设备上生成视频的阶段(尚未上传到服务器)。就像SO上的这些链接一样,我已经破解了一些每隔五秒钟就会关闭AssetWriters的代码。

  • 从iPhone上传直播视频,如Ustream或Qik
  • 从iPhone流式传输视频
  • 从AVAssetWriter读取实时H.264输出时数据损坏

现在在开发过程中,我只是将文件保存到本地设备上,并通过XCode Organizer将其取出。然后,我运行苹果的mediafilesegmenter,简单地将它们转换为MPEG2-TS(它们已经低于10秒,所以没有实际的分段发生——我认为它们只是被转换为TS)。我通过编辑在此过程中创建的各种索引文件来构建m3u8(目前也是手动的)。

当我把资产放在服务器上进行测试时,它们大多是正确的流式传输,但我可以判断何时有分段切换,因为音频会短暂下降(可能还有视频,但我不能确定——看起来还可以)。对于从一个输入文件分割的典型HLS流,显然不会发生这种情况。我不知道是什么原因造成的。

你可以在这里打开我在iPhone上的HLS流(你可以在5秒后听到音频下降,10秒左右再次听到)

  • http://cdn.inv3ntion.com/ms/stitch/stitch.html

在我的创建过程中(无论是在设备上还是在后处理中)是否发生了什么事情,导致了短暂的音频下降?我认为在AssetWriter切换期间不会丢弃任何sampleBuffer(请参阅代码)。

- (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");
            }
        }];
    });
}

您可以尝试在m3u8中的每个段之间插入#EXT-X-DISCONTINITY,但我怀疑这是否有效。这里可能会出很多问题。

假设你的音频采样频率为44100kHz,每22微秒就会有一个新的音频采样。在关闭和重新打开文件的过程中,样本肯定会丢失。如果你连接最后的波形,由于这种损失,它会比实时播放得更快。事实上,这可能不是一个问题。

正如@vipw所说,您还会遇到时间戳问题。每次启动新的mp4时,都是从时间戳零开始的。因此,玩家会感到困惑,因为时间戳不断被重置。

此外,还有传输流格式。TS将每个帧封装到"流"中。HLS通常有4个(PAT、PMT、音频和视频),每个流被分成188字节的分组,分组具有4字节的报头。标头有一个每流4位的连续性计数器,该计数器在溢出时进行封装。因此,在每个mp4上运行mediafilesegmenter,通过将连续性计数器重新设置为零,可以中断每个段的流。

您需要一个接受mp4并创建流输出的工具,该工具可以维护/重写时间戳(PTS、DTS、CTS)以及连续性计数器。

移动数据包

我们在使用旧版本的ffmpeg-pts过滤器来转移数据包时遇到了问题。最近的ffmpeg1和ffmpeg2支持mpegts的时移。

下面是一个ffmpeg调用的示例,注意-t表示持续时间,-ininitial_time表示命令末尾的shift(继续向右滚动…)这是一个具有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

我在github上更新了c++分段器,但它只在纯视频的mpegts中进行了合理的测试。AV仍然会给它带来一些问题(我不确定哪种类型的数据包应该被转移到新的值——第一个视频或第一个音频数据包,选择了第一个视频数据包)。此外,正如你在文章中指出的那样,当你遇到它时,某些媒体可能会出现问题。

如果我有更多的时间,我想调试你的具体案例,并改进c++移位器。我希望上面的ffmpeg示例能帮助您的http实时流媒体示例正常工作,我们已经经历了流媒体的麻烦。我们目前正在处理一个从移动片段中出现的音频流行。修复方法是在分割成分段流之前收集所有源媒体(我们可以在完成视频时这样做,但在迭代构建过程中会减慢速度)。

我认为您的ts文件不会在同一时间线上创建。ts文件中是数据包的表示时间戳,如果在每个段上创建一个新的ts,则可能存在不连续性。

您可以将录制的片段连接在一起,这样新的部分就可以在同一时间轴上加上时间戳。然后分段应该正常工作,并且在生成的流中分段转换应该是平滑的。

我认为您需要一个始终保留前一段的最后部分的过程,以便时间戳始终同步。

相关内容

  • 没有找到相关文章

最新更新