私はAV Foundationクラスを使用して、カメラからライブビデオストリームをキャプチャし、ビデオサンプルを処理しています。これはうまくいきます。ただし、完了したら、AV Foundationインスタンス(キャプチャセッション、プレビューレイヤー、入力と出力)を適切に解放するのに問題があります。
セッションと関連するすべてのオブジェクトが不要になったら、キャプチャセッションを停止して解放します。これはほとんどの場合機能します。ただし、ディスパッチキューによって作成された2番目のスレッド(およびビデオサンプルが処理される場所)で生成されたEXEC_BAD_ACCESS信号でアプリがクラッシュする場合があります。クラッシュの主な原因は、サンプルバッファーデリゲートとして機能し、キャプチャセッションを停止した後に解放された自分のクラスインスタンスです。
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];
これが私がこれまでに見つけた最良の解決策です。基本的な考え方は、ディスパッチキューのファイナライザを使用することです。ディスパッチキューが終了すると、サンプルバッファーが処理される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つの異なるスレッドから解放されることです。これは信頼できますか、それともクラッシュを引き起こす次の問題ですか?
私はApple開発者フォーラムに非常に類似した質問を投稿し、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番目のスレッドがいつ終了したかを推測するだけなので、ディスパッチキューファイナライザを使用したアプローチの方がいいです。
現在のようにApple docs( 1 )[AVCaptureSession stopRunning]
は、レシーバーが完全に停止するまでブロックする同期操作です。したがって、これらすべての問題が発生することはありません。
-(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;
}
他のビューをポップしてプッシュする前に、この関数を呼び出しました。メモリ不足の警告の問題を解決しました。
解決しました!おそらくそれは、セッションを初期化する際の一連のacionsです。これは私にとってはうまくいきます:
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];
ところで、このシーケンスは同期通知の問題を解決するようです:)
キューファイナライザを使用すると、各キューに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];
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の非同期の仮定にもかかわらず。
動作しますが、トリックが表示された場合はお知らせください。非同期で操作することをお勧めします。
ありがとう