AVFoundation,如何在调用“captureStillImageAsynchronouslyFromConnection”时关闭快门声?

94

6
例如在日本,即使你将手机静音,也无法关闭快门声。据我所知,那里有一些奇怪的规定以防止侵犯隐私,比如偷拍照片。因此,我认为没有办法关闭快门声。 - Dunkelstern
3
是的,可能是因为你的iPhone不支持静音拍照。我在美国使用的iPhone在将手机静音时拍照没有声音,但我的女友使用的韩国版iPhone在静音时会有声音。 - donkim
5
明确一点,这是针对手机未静音/振动模式的解决方案。在该模式下,拍照时不会发出任何声音。 - New Alexandria
32
我听说在日本,相机的振动模式下也会有快门声。这是法律要求。 - k06a
3
顺便提一下,如果设备静音了,AudioServicesPlaySystemSound将无法播放声音。所以即使日本设备被静音了,它仍然会发出声音,但非静音时不会。 - Filip Radelic
显示剩余2条评论
9个回答

1504

我曾经用过这段代码来捕获iOS默认快门声音(这里是声音文件名称列表https://github.com/TUNER88/iOSSystemSoundsLibrary):

NSString *path = @"/System/Library/Audio/UISounds/photoShutter.caf";
NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSData *data = [NSData dataWithContentsOfFile:path];
[data writeToFile:[docs stringByAppendingPathComponent:@"photoShutter.caf"] atomically:YES];

然后我使用第三方应用程序从文档目录(Mac的DiskAid)中提取了photoShutter.caf。接下来,我在Audacity音频编辑器中打开了photoShutter.caf并应用了反转效果,在高放大倍数下看起来像这样:

enter image description here

然后我将这个声音保存为photoShutter2.caf,并尝试在captureStillImageAsynchronouslyFromConnection之前播放此声音:

static SystemSoundID soundID = 0;
if (soundID == 0) {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"photoShutter2" ofType:@"caf"];
    NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)filePath, &soundID);
}
AudioServicesPlaySystemSound(soundID);

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:
...

这真的有效!我测试了多次,每次都没有听到快门声音 :)

您可以从此链接获取在 iPhone 5S iOS 7.1.1 上捕获的已反转的声音:https://www.dropbox.com/s/1echsi6ivbb85bv/photoShutter2.caf


105
非常酷。唯一的注意事项是,启用闪光灯时会导致双重快门声,因为调用captureStillImageAsynchronouslyFromConnection:时不会播放默认系统声音,而是在实际捕获图像时(在闪光灯序列期间)才会播放。相反,在self.stillImageOutput上添加一个键值观察者,以检测何时实际开始捕获图像,并在回调方法中播放反向声音。 - Jeff Mascia
17
现代军用飞机如何打败雷达?这就是为什么你在新战斗机设计上看不到“隐形外形”剪影的原因:有一个电子装置来处理基本上是打败雷达的相移,它广播一个“反向”的(相位移)重复信号。 - L0j1k
5
取决于你所讨论的是哪种隐形舰艇。F22及其同类并不是为了变得不可发现而感兴趣,而是为了变得无法被锁定。相位移技术的问题在于,从其他位置来看,你会非常明显,因为它们看不到同样的移位。 - Guvante
14
这就是降噪耳机的工作原理,只不过它们实时捕捉声音。 - Daniel Serodio
4
这对iOS7来说很好用,但在iOS8上不再适用,有什么办法可以在iOS8上解决这个问题吗? - Ticko
显示剩余28条评论

50

我的 Swift 解决方案

当您调用 AVCapturePhotoOutput.capturePhoto 方法捕获图像时,如下面的代码所示。

photoOutput.capturePhoto(with: self.capturePhotoSettings, delegate: self)

AVCapturePhotoCaptureDelegate方法将被调用。 在willCapturePhotoFor方法被调用后,系统会尝试播放快门声音。 因此,您可以在willCapturePhotoFor方法中处理系统声音。

输入图像描述

extension PhotoCaptureService: AVCapturePhotoCaptureDelegate {

    func photoOutput(_ output: AVCapturePhotoOutput, willCapturePhotoFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
        // dispose system shutter sound
        AudioServicesDisposeSystemSoundID(1108)
    }
}

另请参阅


5
这是一个很棒的解决方案,而且完美地发挥作用。回答也非常好。感谢 @kyle。 - Warpling
1
这个完全可以工作。如果你需要分离静音模式和普通模式,反向使用AudioServicesPlaySystemSoundID(1108)即可。谢谢。 - MJ Studio
1
它有效!我想知道您是否使用这种方法上传到AppStore时遇到任何困难? - Liew Jun Tung
3
没问题,我已经使用这个解决方案上传了应用程序。许多应用程序都使用了这种解决方案。祝你编码愉快。 - Won
2
这个答案通常有效,但它会创建一个依赖于系统负载的竞态条件。有时候,iOS 直到快门声的一部分已经播放后才开始调用 photoOutput - Edward Brey
显示剩余3条评论

21

方法1:不确定是否有效,但是在发送捕获事件之前尝试播放空白音频文件。

要播放音频剪辑,请添加Audio Toolbox框架,#include <AudioToolbox/AudioToolbox.h>并在拍照之前立即像这样播放音频文件:

 NSString *path = [[NSBundle mainBundle] pathForResource:@"blank" ofType:@"wav"];
 SystemSoundID soundID;
 NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
 AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
 AudioServicesPlaySystemSound(soundID);

如果需要,这里有一个空白音频文件。 https://d1sz9tkli0lfjq.cloudfront.net/items/0Y3Z0A1j1H2r1c0z3n3t/blank.wav

方法2:如果这个不起作用,还有一个替代方案可供选择。只要你不需要高分辨率,就可以从视频流中抓取一帧,以此避免声音和图像的问题。

方法3:另一种方法是截取应用程序的“屏幕截图”。做法如下:

UIGraphicsBeginImageContext(self.window.bounds.size);
[self.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData * data = UIImagePNGRepresentation(image);
[data writeToFile:@"foo.png" atomically:YES];
如果您想要让预览视频流充满整个屏幕,以便截图更好看:
AVCaptureSession *captureSession = yourcapturesession;
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
UIView *aView = theViewYouWantTheLayerIn;
previewLayer.frame = aView.bounds; // Assume you want the preview layer to fill the view.
[aView.layer addSublayer:previewLayer];

2
我认为那样做行不通,因为快门声会播放,而它只会播放一个“空白”声音。同时播放两个声音是完全可能的。其他两种方法似乎可行! - Linuxmint
2
试一试吧。我不太有信心它会成功,但还是希望能行! - sudo rm -rf

7

我通过在snapStillImage函数中使用以下代码使其工作,对于我的iOS 8.3 iPhone 5来说完美地工作。我还确认如果您使用此代码,苹果不会拒绝您的应用程序(他们没有拒绝我的)。

MPVolumeView* volumeView = [[MPVolumeView alloc] init];
//find the volumeSlider
UISlider* volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        volumeViewSlider = (UISlider*)view;
        break;
    }
}
// mute it here:
[volumeViewSlider setValue:0.0f animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];

请记得在您的应用程序返回时,友好地取消静音!


5

我住在日本,因为安全原因无法在拍照时静音。然而,在视频中,声音会关闭。我不明白为什么。

我拍摄不带快门声的照片的唯一方法是使用AVCaptureVideoDataOutput或AVCaptureMovieFileOutput。对于分析静态图像,AVCaptureVideoDataOutput是唯一的方法。在AVFoundatation示例代码中,

AVCaptureVideoDataOutput *output = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
// If you wish to cap the frame rate to a known value, such as 15 fps, set 
// minFrameDuration.
output.minFrameDuration = CMTimeMake(1, 15);

当我设置CMTimeMake(1, 1); // 每秒一帧时,我的3GS非常卡顿。

在2010年WWDC示例代码FindMyiCone中,我发现了以下代码:

[output setAlwaysDiscardsLateVideoFrames:YES];

当使用此API时,时间不被保证,但是API会按顺序依次调用。我认为这是最好的解决方案。


4

3

您还可以从视频流中获取一帧,以捕获(不是全分辨率的)图像。

在此处使用它来短时间间隔捕获图像:

- (IBAction)startStopPictureSequence:(id)sender
{
    if (!_capturingSequence)
    {
        if (!_captureVideoDataOutput)
        {
            _captureVideoDataOutput = [AVCaptureVideoDataOutput new];
            _captureVideoDataOutput.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};
            [_captureVideoDataOutput setSampleBufferDelegate:self
                                                       queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)];
            if (_sequenceCaptureInterval == 0)
            {
                _sequenceCaptureInterval = 0.25;
            }
        }

        if ([_captureSession canAddOutput:_captureVideoDataOutput])
        {
            [_captureSession addOutput:_captureVideoDataOutput];
            _lastSequenceCaptureDate = [NSDate date]; // Skip the first image which looks to dark for some reason
            _sequenceCaptureOrientation = (_currentDevice.position == AVCaptureDevicePositionFront ? // Set the output orientation only once per sequence
                                           UIImageOrientationLeftMirrored :
                                           UIImageOrientationRight);
            _capturingSequence = YES;
        }
        else
        {
            NBULogError(@"Can't capture picture sequences here!");
            return;
        }
    }
    else
    {
        [_captureSession removeOutput:_captureVideoDataOutput];
        _capturingSequence = NO;
    }
}

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    // Skip capture?
    if ([[NSDate date] timeIntervalSinceDate:_lastSequenceCaptureDate] < _sequenceCaptureInterval)
        return;

    _lastSequenceCaptureDate = [NSDate date];

    UIImage * image = [self imageFromSampleBuffer:sampleBuffer];
    NBULogInfo(@"Captured image: %@ of size: %@ orientation: %@",
               image, NSStringFromCGSize(image.size), @(image.imageOrientation));

    // Execute capture block
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       if (_captureResultBlock) _captureResultBlock(image, nil);
                   });
}

- (BOOL)isRecording
{
    return _captureMovieOutput.recording;
}

你能在使用AVCaptureMovieFileOutput的同时让AVCaptureVideoDataOutput正常工作吗?当我尝试同时使用它们时,AVCaptureVideoDataOutput的委托方法没有被调用。 - Paul Cezanne
抱歉,我还没有尝试过同时拥有多个输出。 - Rivera

0
我能想到的唯一解决办法可能是当他们按下“拍照”按钮时将iPhone静音,然后一秒钟后再取消静音。

如何通过编程将iPhone静音?谢谢! - ohho
即使你能够做到,苹果也不会批准。 - user207616

0
在这种情况下,常见的技巧是找出框架是否调用了某个方法来处理此事件,然后临时覆盖该方法,从而使其失效。
很抱歉,我不是一个足够好的黑客,无法立即告诉您这在这种情况下是否有效。您可以尝试在框架可执行文件上使用“nm”命令,看看是否有一个具有合适名称的命名函数,或者使用带有模拟器的gdb跟踪它的位置。
一旦您知道要覆盖什么,就可以使用那些低级别的ObjC分派函数来重定向函数查找,我相信我曾经做过一次,但是记不清细节了。
希望您能利用我的提示在谷歌上找到一些解决方案。祝你好运。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接