从AVAssetWriter读取实时H.264输出时数据损坏

Chr*_*ger 5 objective-c nsdata h.264 grand-central-dispatch avassetwriter

我正在使用一些技巧来尝试在写入磁盘时读取AVAssetWriter的原始输出.当我通过连接它们重新组装单个文件时,生成的文件与AVAssetWriter的输出文件具有相同的确切字节数.但是,重新组装的文件将无法在QuickTime中播放或由FFmpeg解析,因为存在数据损坏.这里和那里的几个字节已经改变,使得结果文件无法使用.我假设这发生在每次读取的EOF边界上,但它不是一致的损坏.

我计划最终使用与此类似的代码从编码器中解析出单个H.264 NAL单元以将它们打包并通过RTP发送它们,但是如果我不相信从磁盘读取的数据,我可能不得不使用另一种解决方案.

是否有解释/修复此数据损坏?您是否有任何其他资源/链接可以解析如何解析NAL单元以通过RTP进行打包?

完整代码:AVAppleEncoder.m

// Modified from
// http://www.davidhamrick.com/2011/10/13/Monitoring-Files-With-GCD-Being-Edited-With-A-Text-Editor.html
- (void)watchOutputFileHandle
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    int fildes = open([[movieURL path] UTF8String], O_EVTONLY);

    source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,fildes,
                                                              DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
                                                              queue);
    dispatch_source_set_event_handler(source, ^
                                      {
                                          unsigned long flags = dispatch_source_get_data(source);
                                          if(flags & DISPATCH_VNODE_DELETE)
                                          {
                                              dispatch_source_cancel(source);
                                              //[blockSelf watchStyleSheet:path];
                                          }
                                          if(flags & DISPATCH_VNODE_EXTEND)
                                          {
                                              //NSLog(@"File size changed");
                                              NSError *error = nil;
                                              NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:movieURL error:&error];
                                              if (error) {
                                                  [self showError:error];
                                              }
                                              [fileHandle seekToFileOffset:fileOffset];
                                              NSData *newData = [fileHandle readDataToEndOfFile];
                                              if ([newData length] > 0) {
                                                  NSLog(@"newData (%lld): %d bytes", fileOffset, [newData length]);
                                                  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                                                  NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
                                                  NSString *movieName = [NSString stringWithFormat:@"%d.%lld.%d.mp4", fileNumber, fileOffset, [newData length]];
                                                  NSString *path = [NSString stringWithFormat:@"%@/%@", basePath, movieName];
                                                  [newData writeToFile:path atomically:NO];
                                                  fileNumber++;
                                                  fileOffset = [fileHandle offsetInFile];
                                              }
                                          }
                                      });
    dispatch_source_set_cancel_handler(source, ^(void) 
                                       {
                                           close(fildes);
                                       });
    dispatch_resume(source);
}
Run Code Online (Sandbox Code Playgroud)

以下是我发现的一些类似问题,但并未完全回答我的问题:

当我最终想到这一点时,我将发布一个开源库来帮助将来尝试这样做的人.

谢谢!

更新:腐败不会发生在EOF边界.似乎文件的一部分在finishWriting被调用后被重写.第一个文件以4KB的形式分块,因此更改的区域不在EOF边界附近.在movieFragmentInterval启用时,它似乎在新的"moov"元素附近被破坏了.

破碎的文件 左侧是正确的文件,右侧是破损的文件.

Chr*_*ger 2

我最终放弃了“边写边读”的方法,转而采用手动分块方法,finishWriting在后台线程上每 5 秒调用一次。使用最初描述的方法,我能够丢弃可以忽略不计的帧数:

- (void) segmentRecording:(NSTimer*)timer {
    AVAssetWriter *tempAssetWriter = self.assetWriter;
    AVAssetWriterInput *tempAudioEncoder = self.audioEncoder;
    AVAssetWriterInput *tempVideoEncoder = self.videoEncoder;
    self.assetWriter = queuedAssetWriter;
    self.audioEncoder = queuedAudioEncoder;
    self.videoEncoder = queuedVideoEncoder;
    //NSLog(@"Switching encoders");

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [tempAudioEncoder markAsFinished];
        [tempVideoEncoder markAsFinished];
        if (tempAssetWriter.status == AVAssetWriterStatusWriting) {
            if(![tempAssetWriter finishWriting]) {
                [self showError:[tempAssetWriter error]];
            }
        }
        if (self.readyToRecordAudio && self.readyToRecordVideo) {
            NSError *error = nil;
            self.queuedAssetWriter = [[AVAssetWriter alloc] initWithURL:[self newMovieURL] fileType:(NSString *)kUTTypeMPEG4 error:&error];
            if (error) {
                [self showError:error];
            }
            self.queuedVideoEncoder = [self setupVideoEncoderWithAssetWriter:self.queuedAssetWriter formatDescription:videoFormatDescription bitsPerSecond:videoBPS];
            self.queuedAudioEncoder = [self setupAudioEncoderWithAssetWriter:self.queuedAssetWriter formatDescription:audioFormatDescription bitsPerSecond:audioBPS];
            //NSLog(@"Encoder switch finished");

        }
    });
}
Run Code Online (Sandbox Code Playgroud)

完整源代码:https://github.com/chrisballinger/FFmpeg-iOS-Encoder/blob/master/AVSegmentingAppleEncoder.m