更新 - 以下のコードのいくつかの間違いを修正し、画像は他のデバイスに表示されますが、別の問題があります。ビデオ キャプチャが開いている間、「マスター」デバイスは継続的にデータを送信します。このキャプチャが「スレーブ」デバイスに表示される場合があり、非常に短時間で画像が「点滅」して空白になり、これが短時間の間ずっと繰り返されます。これについて何か考えはありますか?
ライブ カメラ キャプチャとライブ マイク キャプチャをネットワーク内の別のデバイスに送信する必要があるアプリに取り組んでいます。
TCP サーバーを使用してデバイス間の接続を行い、それを bonjour で公開しました。これは魅力のように機能します。
最も重要な部分は、「マスター」デバイスからビデオとオーディオを送受信し、「スレーブ」デバイスでレンダリングすることです。
まず、アプリがカメラのサンプル バッファーを取得し、UIImage に変換するコードを次に示します。
@implementation AVCaptureManager (AVCaptureVideoDataOutputSampleBufferDelegate)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
dispatch_sync(dispatch_get_main_queue(), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
NSData *data = UIImageJPEGRepresentation(image, 0.2);
[self.delegate didReceivedImage:image];
[self.delegate didReceivedFrame:data];
[pool drain];
});
}
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer, 0);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
CGContextRef context = CGBitmapContextCreate(
baseAddress,
width,
height,
8,
bytesPerRow,
colorSpace,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
);
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
UIImage *image = [UIImage imageWithCGImage:quartzImage];
CGImageRelease(quartzImage);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return image;
}
@end
メッセージ「[self.delegate didReceivedImage:image];」マスターデバイスでイメージキャプチャをテストするだけで、このイメージはキャプチャデバイスで機能します。
次は、ネットワークに送信する方法についてです。
- (void) sendData:(NSData *)data
{
if(_outputStream && [_outputStream hasSpaceAvailable])
{
NSInteger bytesWritten = [_outputStream write:[data bytes] maxLength:[data length]];
if(bytesWritten < 0)
NSLog(@"[ APP ] Failed to write message");
}
}
RunLoop を使用してストリームの書き込みと読み取りを行っていることを確認してください。これは、常にストリームを開いたり閉じたりするよりも優れていると思います。
次に、スレーブ デバイスで「NSStreamEventHasBytesAvailable」イベントを受け取ります。これを処理するコードは次のとおりです。
case NSStreamEventHasBytesAvailable:
/*I can't to start a case without a expression, why not?*/
NSLog(@"[ APP ] stream handleEvent NSStreamEventHasBytesAvailable");
NSUInteger bytesRead;
uint8_t buffer[BUFFER_SIZE];
while ([_inputStream hasBytesAvailable])
{
bytesRead = [_inputStream read:buffer maxLength:BUFFER_SIZE];
NSLog(@"[ APP ] bytes read: %i", bytesRead);
if(bytesRead)
[data appendBytes:(const void *)buffer length:sizeof(buffer)];
}
[_client writeImageWithData:data];
break;
BUFFER_SIZE の値は 32768 です。while ブロックは必要ないと思いますが、最初の反復で使用可能なすべてのバイトを読み取れなくても、次の反復で読み取ることができるため、while ブロックを使用します。
ここがポイントです。ストリームは正しく来ますが、NSData でシリアル化されたイメージが壊れているようです。次に、クライアントにデータを送信するだけです...
[_client writeImageWithData:data];
...そして、このように単純なクライアントクラスのデータでUIImageを作成します...
[camPreview setImage:[UIImage imageWithData:data]];
camPreview (はい UIImageView です) には、プレースホルダーを画面に表示するためだけの画像があります。ネットワークから画像を取得して camPreview に渡すと、プレースホルダーが空白になります。
他の考えは、出力についてです。キャプチャを開始すると、データを受け取る最初の部分で、システムから次のメッセージが表示されます。
エラー: ImageIO: JPEG 破損した JPEG データ: マーカー 0xbf の前に余分な 28 バイトがあります
エラー: ImageIO: JPEG サポートされていないマーカー タイプ 0xbf
しばらくすると、このメッセージが表示されなくなります。
ポイントは、「スレーブ」デバイスで画像が表示されない原因を突き止めることです。
ありがとう。