我正试图在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,则可能存在不连续性。
您可以将录制的片段连接在一起,这样新的部分就可以在同一时间轴上加上时间戳。然后分段应该正常工作,并且在生成的流中分段转换应该是平滑的。
我认为您需要一个始终保留前一段的最后部分的过程,以便时间戳始终同步。