我正在尝试使用AVAssetWriter编码视频。我正在使用ARC。我想做的是通过套接字流视频。

现在是问题。我第一次进行的设置有效。 ,如果不先重新启动应用程序,我将无法再次使用AVAssetWriter。如果不重新启动应用程序,则在调用[m_writer startWriting]时收到以下错误:

Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x14d06fe0 {Error Domain=NSOSStatusErrorDomain Code=-12983 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-12983)}
  • 我确保在调用cleaup方法时删除了该文件。
    我使用iExplorer导航到文件所在的临时文件夹
    正在创建。我还确保了AVAssetWriter的状态
    调用finshWriting之后是AVAssetWriterStatusFinished。

  • 编辑:即使使用其他文件名,该错误仍然存​​在。

    我创建了VideoEncoder类的实例(下面的代码),然后将其提供给原始图像。然后我等待AVAssetWriter使用dispatch_source_set_event_handler()将某些东西写入输出文件,即:
    m_inputFile = [NSFileHandle fileHandleForReadingAtPath: m_writer.path];
    m_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [m_inputFile fileDescriptor], 0, m_readQueue);
    dispatch_source_set_event_handler(m_readSource, ^{
      // Read bytes from file and send over socket
    }
    dispatch_source_set_cancel_handler(m_readSource, ^{
      [self cleanup];
    }
    dispatch_resume(m_readSource);
    

    我为AVAssetWriter创建了一个名为VideoEncoder的包装器。这是我初始化AVAssetWriter的方法:
    @implementation VideoEncoder : NSObject
    {
      AVAssetWriter* m_writer;
      AVAssetWriterInput* m_writerInput;
      AVAssetWriterInputPixelBufferAdaptor* m_pixelBufferAdaptor;
    }
    -(id) initWithPath:(NSString*)path  width:(int)width height:(int)height parameters:(NSDictionary*) parameters
    {
      if (self = [super init])
      {
        self.path = path;
        [[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
        NSURL* url = [NSURL fileURLWithPath: self.path];
    
        m_writer = [[AVAssetWriter alloc] initWithURL:url fileType:AVFileTypeQuickTimeMovie error: nil];
    
        NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys:
                              AVVideoCodecH264, AVVideoCodecKey,
                              [NSNumber numberWithInt: width], AVVideoWidthKey,
                              [NSNumber numberWithInt:height], AVVideoHeightKey,
                              parameters,                      AVVideoCompressionPropertiesKey,
                              nil];
        m_writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
        m_writerInput.expectsMediaDataInRealTime = YES;
    
        [m_writer addInput:m_writerInput];
    
        NSDictionary* pixelBufferOptions = [[NSDictionary alloc] initWithObjectsAndKeys:
                                            [NSNumber numberWithInt: kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
                                            [NSNumber numberWithInt: width*4],                   kCVPixelBufferBytesPerRowAllignmentKey,
                                            [NSNumber numberWithInt; width],                     kCVPixelBufferWidthKey,
                                            [NSNumber numberWithInt; height],                     kCVPixelBufferHeightKey,
                                            nil];
    
        m_pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc]
                                initWithAssetWriterInput: m_writerInput
                                sourcePixelBufferAttributes: pixelBufferOptions];
    
        [m_writer startWriting];
        [m_writer startSessionAtSourceTime: kCMTimeZero];
      }
    }
    

    这是我喂它原始图像的方式:
    -(BOOL) encodeFrame:(CVPixelBufferRead)pixelBuffer time(CMTime)time;
    {
      if (m_writer.status == AVAssetWriterStatusFailed)
      {
        return NO;
      }
      if (m_writerInput.readyForMoreMediaData == YES)
      {
        if ([m_pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:time])
        {
          return YES:
        }
      }
      return NO;
    }
    

    这是生成像素缓冲区的方式:
    -(bool) encodeImage:(const char*) frameBuffer width:(int)width height:(int)height
    {
      CVPixelBufferRef pixelBuffer = NULL;
      CVReturn ret;
    
      ret = CVPixelBufferCraeteWithBytes(
               kCFAllocatorDefault,
               with,
               height,
               kCVPixelFormatType_32BGRA,
               (void*) frameBuffer,
               width * 4,
               NULL, NULL, NULL, NULL, &pixelBuffer);
      if (ret == kCVReturnSuccess && pixelBuffer)
      {
        CFTimeInterval currTime = CACurrentMediaTime();
        if (m_frameIndex > 0 )
        {
          int ticks = (int)((currTime - m_prevTime) * 1000) / 30;
          CMTime addTime;
    
          ticks = ticks <= 0 ? 1 : ticks;
          addTime = CMTimeMake(ticks, 30);
          m_timestamp = CMTimeAdd(m_timestamp, addTime);
        }
        m_prevTime = currTime;
        [m_impl encodeFrame: pixelBuffer time: m_timestamp];
    
        CVBufferRelease(pixelBuffer);
        m_frameIndex ++;
      }
    }
    

    这是我终止AVAssetWriter的方式:
    -(void) finishWritingWithCompletionHandler(void (^)(void))handler
    {
      [m_writerInput markAsFinished];
      [m_writer finishWithCompletionHandler: ^{
        handler();
      }
    }
    

    这就是我使用包装纸的方式。我使用这个包装器形式的C++类。实例化:
    -(void) makeFilename
    {
      NSString* filename = [NSString* stringWithFormat: @"temp%d.tmp", 1];
      NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent: filename];
      return path;
    }
    
    bool CFrameEncoder::StartEncoding()
    {
      m_impl = CFRetain((__bridge*void)[[VideoEncoder alloc] initWithPath:[self makeFilepath] width:800 height:480 parameters: params]);
    }
    

    解除分配:
    bool CFrameEncoder::StopDecoding()
    {
      [(__bridge VideoEncoder*) m_impl terminate];
      CFRelease(m_impl);
    }
    

    (VideoEncoder类的)终止函数:
    -(void) terminate
    {
      if (m_readSource)
      {
        dispatch_source_cancel(m_readSource);
      }
    }
    // The dispatch source cancel handler
    -(void) cleanup
    {
      m_readQueue = nil;
      m_readSource = nil;
    
      [m_inputFile closeFile];
    
      NSString* path = m_writer.path;
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
      [m_writer finishWithCompletionHandler:^{
        dispatch_semaphore_signal(semaphore);
      }];
    
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      m_writer = nil;
      [[NSFileManager defaultManager] removeItemAtPath: path error: nil];
    }
    

    最佳答案

    AVAssetWriter类只能包含有限数量的实例。如果您的应用程序超过了此限制,则当您尝试启动写入时,AVAssetWriter会因AVAssetWriterStatusFailed而失败。

    这意味着我的代码中有某些东西阻止了AVAssetWriter的释放。有人知道吗?

    10-08 11:28