web-dev-qa-db-ja.com

avcapturesessionでカメラを切り替える

ここでこのチュートリアルを使用: http://www.musicalgeometry.com/?p=1297AVCaptureSessionを使用してカスタムオーバーレイと画像キャプチャを作成しました。

ユーザーがフロントカメラとバックカメラを切り替えることを許可しようとしています。カメラを切り替えるCaptureSessionManagerのコードは次のとおりです。

- (void)addVideoInputFrontCamera:(BOOL)front {
    NSArray *devices = [AVCaptureDevice devices];
    AVCaptureDevice *frontCamera;
    AVCaptureDevice *backCamera;

    for (AVCaptureDevice *device in devices) {

        //NSLog(@"Device name: %@", [device localizedName]);

        if ([device hasMediaType:AVMediaTypeVideo]) {

            if ([device position] == AVCaptureDevicePositionBack) {
                //NSLog(@"Device position : back");
                backCamera = device;
            }
            else {
                //NSLog(@"Device position : front");
                frontCamera = device;
            }
        }
    }

    NSError *error = nil;

    if (front) {
        AVCaptureDeviceInput *frontFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
        if (!error) {
            if ([[self captureSession] canAddInput:frontFacingCameraDeviceInput]) {
                [[self captureSession] addInput:frontFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add front facing video input");
            }
        }
    } else {
        AVCaptureDeviceInput *backFacingCameraDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:backCamera error:&error];
        if (!error) {
            if ([[self captureSession] canAddInput:backFacingCameraDeviceInput]) {
                [[self captureSession] addInput:backFacingCameraDeviceInput];
            } else {
                NSLog(@"Couldn't add back facing video input");
            }
        }
    }
}

カスタムオーバーレイコントローラーで、viewDidLoadのようにすべてを初期化します。

[self setCaptureManager:[[CaptureSessionManager alloc] init]];

[[self captureManager] addVideoInputFrontCamera:NO]; // set to YES for Front Camera, No for Back camera

[[self captureManager] addStillImageOutput];

[[self captureManager] addVideoPreviewLayer];
CGRect layerRect = [[[self view] layer] bounds];
[[[self captureManager] previewLayer] setBounds:layerRect];
[[[self captureManager] previewLayer] setPosition:CGPointMake(CGRectGetMidX(layerRect),CGRectGetMidY(layerRect))];
[[[self view] layer] addSublayer:[[self captureManager] previewLayer]];

[[_captureManager captureSession] startRunning];

カメラ切り替えボタンは、switchCameraというメソッドに接続されています。私はこれを試しました:

- (void)switchCameraView:(id)sender {

    [[self captureManager] addVideoInputFrontCamera:YES]; // set to YES for Front Camera, No for Back camera

}

これを呼び出すと、NSLogからCaptureSessionManagerのエラーが発生し、その理由がわかりません。 viewDidLoadで、fontCameraYESに設定すると、前面カメラは表示されますが、背面カメラに切り替えることはできません。

適切に切り替える方法についてのアイデアはありますか?

52
Kyle Begeman

最初に既存のAVCameraInputをAVCaptureSessionから削除してから、新しいAVCameraInputをAVCaptureSessionに追加する必要があります。私にとっては次のように動作します(ARCの下):

-(IBAction)switchCameraTapped:(id)sender
{
    //Change camera source
    if(_captureSession)
    {
        //Indicate that some changes will be made to the session
        [_captureSession beginConfiguration];

        //Remove existing input
        AVCaptureInput* currentCameraInput = [_captureSession.inputs objectAtIndex:0];
        [_captureSession removeInput:currentCameraInput];

        //Get new input
        AVCaptureDevice *newCamera = nil;
        if(((AVCaptureDeviceInput*)currentCameraInput).device.position == AVCaptureDevicePositionBack)
        {
            newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];
        }
        else
        {
            newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];
        }

        //Add input to session
        NSError *err = nil;
        AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:newCamera error:&err];
        if(!newVideoInput || err)
        {
            NSLog(@"Error creating capture device input: %@", err.localizedDescription);
        }
        else
        {
            [_captureSession addInput:newVideoInput];
        }

        //Commit all the configuration changes at once
        [_captureSession commitConfiguration];
    }
}

// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position
{
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *device in devices) 
    {
        if ([device position] == position) return device;
    }
    return nil;
}
100
NES_4Life

Swift 4

@IBAction func switchCameraTapped(sender: Any) {
    //Change camera source
    if let session = captureSession {
        //Indicate that some changes will be made to the session
        session.beginConfiguration()

        //Remove existing input
        guard let currentCameraInput: AVCaptureInput = session.inputs.first else {
            return
        }

        session.removeInput(currentCameraInput)

        //Get new input
        var newCamera: AVCaptureDevice! = nil
        if let input = currentCameraInput as? AVCaptureDeviceInput {
            if (input.device.position == .back) {
                newCamera = cameraWithPosition(position: .front)
            } else {
                newCamera = cameraWithPosition(position: .back)
            }
        }

        //Add input to session
        var err: NSError?
        var newVideoInput: AVCaptureDeviceInput!
        do {
            newVideoInput = try AVCaptureDeviceInput(device: newCamera)
        } catch let err1 as NSError {
            err = err1
            newVideoInput = nil
        }

        if newVideoInput == nil || err != nil {
            print("Error creating capture device input: \(err?.localizedDescription)")
        } else {
            session.addInput(newVideoInput)
        }

        //Commit all the configuration changes at once
        session.commitConfiguration()
    }
}

// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
    let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
    for device in discoverySession.devices {
        if device.position == position {
            return device
        }
    }

    return nil
}

Swift 3編集(François-JulienAlcaraz回答と組み合わせて):

@IBAction func switchCameraTapped(sender: Any) {
    //Change camera source
    if let session = captureSession {
        //Indicate that some changes will be made to the session
        session.beginConfiguration()

        //Remove existing input
        guard let currentCameraInput: AVCaptureInput = session.inputs.first as? AVCaptureInput else {
            return
        }

        session.removeInput(currentCameraInput)

        //Get new input
        var newCamera: AVCaptureDevice! = nil
        if let input = currentCameraInput as? AVCaptureDeviceInput {
            if (input.device.position == .back) {
                newCamera = cameraWithPosition(position: .front)
            } else {
                newCamera = cameraWithPosition(position: .back)
            }
        }

        //Add input to session
        var err: NSError?
        var newVideoInput: AVCaptureDeviceInput!
        do {
            newVideoInput = try AVCaptureDeviceInput(device: newCamera)
        } catch let err1 as NSError {
            err = err1
            newVideoInput = nil
        }

        if newVideoInput == nil || err != nil {
            print("Error creating capture device input: \(err?.localizedDescription)")
        } else {
            session.addInput(newVideoInput)
        }

        //Commit all the configuration changes at once
        session.commitConfiguration()
    }
}

// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(position: AVCaptureDevicePosition) -> AVCaptureDevice? {
    if let discoverySession = AVCaptureDeviceDiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: .unspecified) {
        for device in discoverySession.devices {
            if device.position == position {
                return device
            }
        }
    }

    return nil
}

@ NES_4Lifeの答えの迅速なバージョン:

@IBAction func switchCameraTapped(sender: AnyObject) {
    //Change camera source
    if let session = captureSession {
        //Indicate that some changes will be made to the session
        session.beginConfiguration()

        //Remove existing input
        let currentCameraInput:AVCaptureInput = session.inputs.first as! AVCaptureInput
        session.removeInput(currentCameraInput)

        //Get new input
        var newCamera:AVCaptureDevice! = nil
        if let input = currentCameraInput as? AVCaptureDeviceInput {
            if (input.device.position == .Back)
            {
                newCamera = cameraWithPosition(.Front)
            }
            else
            {
                newCamera = cameraWithPosition(.Back)
            }
        }

        //Add input to session
        var err: NSError?
        var newVideoInput: AVCaptureDeviceInput!
        do {
            newVideoInput = try AVCaptureDeviceInput(device: newCamera)
        } catch let err1 as NSError {
            err = err1
            newVideoInput = nil
        }

        if(newVideoInput == nil || err != nil)
        {
            print("Error creating capture device input: \(err!.localizedDescription)")
        }
        else
        {
            session.addInput(newVideoInput)
        }

        //Commit all the configuration changes at once
        session.commitConfiguration()
    }
}

// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(position: AVCaptureDevicePosition) -> AVCaptureDevice?
{
    let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
    for device in devices {
        let device = device as! AVCaptureDevice
        if device.position == position {
            return device
        }
    }

    return nil
}
22
chengsam

以前の回答に基づいて、いくつかの検証と特定の変更を加えて独自のバージョンを作成しましたが、現在のカメラ入力はキャプチャセッションの入力の最初のオブジェクトではない可能性があるため、これを変更しました:

//Remove existing input
AVCaptureInput* currentCameraInput = [self.captureSession.inputs objectAtIndex:0];
[self.captureSession removeInput:currentCameraInput];

これに(すべてのビデオタイプ入力を削除する):

for (AVCaptureDeviceInput *input in self.captureSession.inputs) {
    if ([input.device hasMediaType:AVMediaTypeVideo]) {
        [self.captureSession removeInput:input];
        break;
    }
}

コード全体は次のとおりです。

if (!self.captureSession) return;

[self.captureSession beginConfiguration];

AVCaptureDeviceInput *currentCameraInput;

// Remove current (video) input
for (AVCaptureDeviceInput *input in self.captureSession.inputs) {
    if ([input.device hasMediaType:AVMediaTypeVideo]) {
        [self.captureSession removeInput:input];

        currentCameraInput = input;
        break;
    }
}

if (!currentCameraInput) return;

// Switch device position
AVCaptureDevicePosition captureDevicePosition = AVCaptureDevicePositionUnspecified;
if (currentCameraInput.device.position == AVCaptureDevicePositionBack) {
    captureDevicePosition = AVCaptureDevicePositionFront;
} else {
    captureDevicePosition = AVCaptureDevicePositionBack;
}

// Select new camera
AVCaptureDevice *newCamera;
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for (AVCaptureDevice *captureDevice in devices) {
    if (captureDevice.position == captureDevicePosition) {
        newCamera = captureDevice;
    }
}

if (!newCamera) return;

// Add new camera input
NSError *error;
AVCaptureDeviceInput *newVideoInput = [[AVCaptureDeviceInput alloc] initWithDevice:newCamera error:&error];
if (!error && [self.captureSession canAddInput:newVideoInput]) {
    [self.captureSession addInput:newVideoInput];
}

[self.captureSession commitConfiguration];
11
Fantini

Swift 3

func switchCamera() {
        session?.beginConfiguration()
        let currentInput = session?.inputs.first as? AVCaptureDeviceInput
        session?.removeInput(currentInput)

        let newCameraDevice = currentInput?.device.position == .back ? getCamera(with: .front) : getCamera(with: .back)
        let newVideoInput = try? AVCaptureDeviceInput(device: newCameraDevice)
        session?.addInput(newVideoInput)
        session?.commitConfiguration()
    }

// MARK: - Private
extension CameraService {
    func getCamera(with position: AVCaptureDevicePosition) -> AVCaptureDevice? {
        guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else {
            return nil
        }

        return devices.filter {
            $0.position == position
        }.first
    }
}

Swift 4

これで完全な実装を確認できます Gist

4
Bohdan Savych

非推奨の警告なしのcameraWithPositionのSwift 3バージョン:

    // Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(_ position: AVCaptureDevicePosition) -> AVCaptureDevice?
{
    if let deviceDescoverySession = AVCaptureDeviceDiscoverySession.init(deviceTypes: [AVCaptureDeviceType.builtInWideAngleCamera],
                                                          mediaType: AVMediaTypeVideo,
                                                          position: AVCaptureDevicePosition.unspecified) {

        for device in deviceDescoverySession.devices {
            if device.position == position {
                return device
            }
        }
    }

    return nil
}

必要に応じて、deviceTypes配列を変更して、iPhone 7+(デュアルカメラ)から新しいdevicesTypesを取得することもできます。

ここに良い読み物があります: https://forums.developer.Apple.com/thread/63347

0
hefgi

以下は、「複数のオーディオ/ビデオAVCaptureInputsは現在サポートされていません」の修正を含む、チェンサムのコードの更新バージョンです。

func switchCameraTapped() {
    //Change camera source
    //Indicate that some changes will be made to the session
    session.beginConfiguration()

    //Remove existing input
    guard let currentCameraInput: AVCaptureInput = session.inputs.first else {
        return
    }


    //Get new input
    var newCamera: AVCaptureDevice! = nil
    if let input = currentCameraInput as? AVCaptureDeviceInput {
        if (input.device.position == .back) {
            newCamera = cameraWithPosition(position: .front)
        } else {
            newCamera = cameraWithPosition(position: .back)
        }
    }

    //Add input to session
    var err: NSError?
    var newVideoInput: AVCaptureDeviceInput!
    do {
        newVideoInput = try AVCaptureDeviceInput(device: newCamera)
    } catch let err1 as NSError {
        err = err1
        newVideoInput = nil
    }

    if let inputs = session.inputs as? [AVCaptureDeviceInput] {
        for input in inputs {
            session.removeInput(input)
        }
    }


    if newVideoInput == nil || err != nil {
        print("Error creating capture device input: \(err?.localizedDescription)")
    } else {
        session.addInput(newVideoInput)
    }

    //Commit all the configuration changes at once
    session.commitConfiguration()
}

// Find a camera with the specified AVCaptureDevicePosition, returning nil if one is not found
func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
    let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: AVMediaType.video, position: .unspecified)
    for device in discoverySession.devices {
        if device.position == position {
            return device
        }
    }

    return nil
}
0
William G