33

AVFoundation クラスを使用して、カメラからライブ ビデオ ストリームをキャプチャし、ビデオ サンプルを処理しています。これはうまく機能します。ただし、完了したら AVFoundation インスタンス (キャプチャ セッション、プレビュー レイヤー、入力および出力) を適切に解放するのに問題があります。

セッションと関連するすべてのオブジェクトが不要になったら、キャプチャ セッションを停止して解放します。これはほとんどの場合機能します。EXEC_BAD_ACCESSただし、ディスパッチ キュー (およびビデオ サンプルが処理される場所) によって作成された 2 番目のスレッドで発生したシグナルで、アプリがクラッシュすることがあります。クラッシュは主に、サンプル バッファー デリゲートとして機能し、キャプチャ セッションを停止した後に解放される、独自のクラス インスタンスが原因です。

Apple のドキュメントには、この問題が記載されています。キャプチャ セッションの停止は非同期操作です。つまり、すぐには起こりません。特に、2 番目のスレッドは引き続きビデオ サンプルを処理し、キャプチャ セッションや入出力デバイスなどのさまざまなインスタンスにアクセスします。

AVCaptureSessionでは、関連するすべてのインスタンスを適切に解放するにはどうすればよいでしょうか? が終了したことを確実に知らせる通知はありAVCaptureSessionますか?

これが私のコードです:

宣言:

AVCaptureSession* session;
AVCaptureVideoPreviewLayer* previewLayer;
UIView* view;

インスタンスのセットアップ:

AVCaptureDevice* camera = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
session = [[AVCaptureSession alloc] init];

AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice: camera error: &error];
[session addInput: input];
AVCaptureVideoDataOutput* output = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
[session addOutput: output];

dispatch_queue_t queue = dispatch_queue_create("augm_reality", NULL);
[output setSampleBufferDelegate: self queue: queue];
dispatch_release(queue);

previewLayer = [[AVCaptureVideoPreviewLayer layerWithSession: session] retain];
previewLayer.frame = view.bounds;
[view.layer addSublayer: previewLayer];

[session startRunning];

掃除:

[previewLayer removeFromSuperlayer];
[previewLayer release];
[session stopRunning];
[session release];
4

7 に答える 7

18

これが私がこれまでに見つけた最良の解決策です。基本的な考え方は、ディスパッチ キューのファイナライザーを使用することです。ディスパッチ キューが終了すると、サンプル バッファーが処理される 2 番目のスレッドでそれ以上のアクションが発生しないことが確実になります。

static void capture_cleanup(void* p)
{
    AugmReality* ar = (AugmReality *)p; // cast to original context instance
    [ar release];  // releases capture session if dealloc is called
}

...

dispatch_queue_t queue = dispatch_queue_create("augm_reality", NULL);
dispatch_set_context(queue, self);
dispatch_set_finalizer_f(queue, capture_cleanup);
[output setSampleBufferDelegate: self queue: queue];
dispatch_release(queue);
[self retain];

...

残念ながら、キャプチャを明示的に停止する必要があります。そうしないと、インスタンスを解放しても解放されません。これは、2 番目のスレッドもカウンターをインクリメントおよびデクリメントするためです。

さらなる問題は、私のクラスが 2 つの異なるスレッドから解放されることです。これは信頼できるものですか、それともクラッシュを引き起こす次の問題ですか?

于 2010-10-04T18:32:29.420 に答える
4

Apple Developer Forum に非常によく似た質問を投稿し、Apple の従業員から回答を得ました。彼はそれが既知の問題だと言います:

これは、iOS 4.0 ~ 4.1 の AVCaptureSession / VideoDataOutput の問題であり、修正されており、今後のアップデートで表示される予定です。当面の間、AVCaptureSession を停止した後、セッションとデータ出力を破棄する前に、短い時間 (0.5 秒など) 待機することで回避できます。

彼/彼女は次のコードを提案します:

dispatch_after(
    dispatch_time(0, 500000000),
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), // or main queue, or your own
    ^{
        // Do your work here.
        [session release];
        // etc.
    }
);

このコードは、2 番目のスレッドがいつ終了したかを推測するだけなので、ディスパッチ キュー ファイナライザーを使用したアプローチの方が気に入っています。

于 2010-10-09T08:33:35.300 に答える
2

解決しました!おそらく、セッションを初期化する際のアクションのシーケンスです。これは私のために働きます:

NSError *error = nil;

if(session)
    [session release];

// Create the session
session = [[AVCaptureSession alloc] init];


// Configure the session to produce lower resolution video frames, if your 
// processing algorithm can cope. We'll specify medium quality for the
// chosen device.
session.sessionPreset = AVCaptureSessionPresetMedium;

// Find a suitable AVCaptureDevice
AVCaptureDevice *device = [AVCaptureDevice
                           defaultDeviceWithMediaType:AVMediaTypeVideo];

// Create a device input with the device and add it to the session.
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device 
                                                                    error:&error];
if (!input) {
    // Handling the error appropriately.
}
[session addInput:input];

// Create a VideoDataOutput and add it to the session
AVCaptureVideoDataOutput *output = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
[session addOutput:output];


// Configure your output.
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);

// Specify the pixel format
output.videoSettings = 
[NSDictionary dictionaryWithObject:
 [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] 
                            forKey:(id)kCVPixelBufferPixelFormatTypeKey];

// If you wish to cap the frame rate to a known value, such as 15 fps, set 
// minFrameDuration.
output.minFrameDuration = CMTimeMake(1, 15);

previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
[delegate layerArrived:previewLayer];

NSNotificationCenter *notify =
[NSNotificationCenter defaultCenter];
[notify addObserver: self
            selector: @selector(onVideoError:)
            name: AVCaptureSessionRuntimeErrorNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStart:)
            name: AVCaptureSessionDidStartRunningNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStop:)
            name: AVCaptureSessionDidStopRunningNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStop:)
            name: AVCaptureSessionWasInterruptedNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStart:)
            name: AVCaptureSessionInterruptionEndedNotification
            object: session];

// Start the session running to start the flow of data
[session startRunning];

ところで、このシーケンスは同期通知の問題を解決するようです:)

于 2010-10-04T10:24:26.910 に答える
2
 -(void)deallocSession
{
[captureVideoPreviewLayer removeFromSuperlayer];
for(AVCaptureInput *input1 in session.inputs) {
    [session removeInput:input1];
}

for(AVCaptureOutput *output1 in session.outputs) {
    [session removeOutput:output1];
}
[session stopRunning];
session=nil;
outputSettings=nil;
device=nil;
input=nil;
captureVideoPreviewLayer=nil;
stillImageOutput=nil;
self.vImagePreview=nil;

}

他のビューをポップしてプッシュする前に、この関数を呼び出しました。メモリ不足の警告の問題を解決しました。

于 2013-12-11T06:47:45.193 に答える
2

キュー ファイナライザを使用すると、各キューに dispatch_semaphore を使用でき、完了したらクリーンアップ ルーチンを続行できます。

#define GCD_TIME(delayInSeconds) dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC)

static void vQueueCleanup(void* context) {
  VideoRecordingViewController *vc = (VideoRecordingViewController*)context;
  if (vc.vSema) dispatch_semaphore_signal(vc.vSema);
}

static void aQueueCleanup(void* context) {
  VideoRecordingViewController *vc = (VideoRecordingViewController*)context;
  if (vc.aSema) dispatch_semaphore_signal(vc.aSema);
}

//In your cleanup method:
vSema = dispatch_semaphore_create(0);
aSema = dispatch_semaphore_create(0);
self.avSession = nil;
if (vSema) dispatch_semaphore_wait(vSema, GCD_TIME(0.5));
if (aSema) dispatch_semaphore_wait(aSema, GCD_TIME(0.5));
[self.navigationController popViewControllerAnimated:YES];

AVCaptureVideoDataOutput/AVCaptureAudioDataOutput オブジェクトのサンプル バッファー デリゲートを nil に設定する必要があることに注意してください。そうしないと、関連するキューが解放されないため、AVCaptureSession を解放するときにファイナライザーが呼び出されません。

[avs removeOutput:vOut];
[vOut setSampleBufferDelegate:nil queue:NULL];
于 2012-01-20T02:45:31.567 に答える
1

AVCaptureSession の割り当て後、以下を使用できます。

NSNotificationCenter *notify =
[NSNotificationCenter defaultCenter];
[notify addObserver: self
            selector: @selector(onVideoError:)
            name: AVCaptureSessionRuntimeErrorNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStart:)
            name: AVCaptureSessionDidStartRunningNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStop:)
            name: AVCaptureSessionDidStopRunningNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStop:)
            name: AVCaptureSessionWasInterruptedNotification
            object: session];
[notify addObserver: self
            selector: @selector(onVideoStart:)
            name: AVCaptureSessionInterruptionEndedNotification
            object: session];

これらは、session.stopRunning、session.startRunning などで関連するメソッドをコールバックしています。

そこには、文書化されていないクリーンアップ ブロックも実装する必要があります。

AVCaptureInput* input = [session.inputs objectAtIndex:0];
[session removeInput:input];
AVCaptureVideoDataOutput* output = (AVCaptureVideoDataOutput*)[session.outputs objectAtIndex:0];
[session removeOutput:output];  

私が混乱しているのは、seesion.stopRunning を呼び出すと、onVideoStop: が同期的に呼び出されることです。ケースに関するAppleの非同期仮定にもかかわらず。

それは機能していますが、何かトリックを見つけた場合はお知らせください。非同期で作業することをお勧めします。

ありがとう

于 2010-09-24T10:01:23.590 に答える