20 декабря 2016 в 14:37

Custom Video Recorder для iOS приложений tutorial

Приложение Камера для iPhone / iPad очень удобно в использовании. Пользователь легко может переключаться из режима фотографирования на видеосъемку. В режиме видеосъемки показывается время съемки и всего одна кнопка (Старт / Стоп). К сожалению, при использовании стандартного UIImagePickerController’а нет возможности контролировать количество кадров в секунду и некоторые другие параметры. Я покажу, как, используя AVFoundation framework, получить доступ к более тонким настройкам камеры, таким как, количество кадров в секунду, качество видео, длительность записи, размер видео файла. Пользователь сразу будет видеть на экране видео в том качестве, в котором оно будет сохранено.

Основной объект, который позволит мне вести видеосъемку:

AVCaptureSession  // сессия захвата видео с камеры

Кроме того, мне понадобятся:

AVCaptureVideoPreviewLayer  // слой, в котором будем показывать видео с камеры в реальном времени
AVCaptureDevice  // устройство захвата видео / аудио
AVCaptureDeviceInput  // вход видео / аудио для AVCaptureSession
AVCaptureMovieFileOutput  // выход AVCaptureSession для записи захваченного видео в файл

Дизайн можно хранить в xib файле или storyboard’е. Используя Autolayout и Constraints в дизайнере можно добиться того, что все панели будут автоматически растягиваться, кнопки выравниваться по центру (левому или правому краю). У нашего VideoRecorderController‘а будет три режима работы:

  1. Готов к съемке: AVCaptureSession запущена, на экране видео с камеры в реальном времени, но запись не идет.

    На нижней панели активна кнопка Cancel — отмена съемки, также активна кнопка Start — начало записи, кнопка Use Video скрыта.

    На верхней панели показано время записи – 00:00. После нажатия кнопки Cancel у делегата видеосъемки срабатывает метод -(void)videoRecorderDidCancelRecordingVideo. После нажатия кнопки Start переходим в следующий режим.

  2. Идет съемка:AVCaptureSession запущена, на экране видео с камеры в реальном времени, при этом идет запись видео в файл. На нижней панели вместо кнопки Start появляется кнопка Stop — конец записи, кнопка Cancel скрыта, кнопка Use Video также скрыта. На верхней панели показано текущее время записи – 00:22. После нажатия кнопки Stop запись останавливается, переходим в следующий режим.

  3. Съемка завершена: AVCaptureSession остановлена, на экране последний кадр отснятого видео, запись видео в файл завершена. По центру экрана появляется кнопка Play Video.
    На нижней панели вместо кнопки Cancel появляется кнопка Retake – переснять видео, появляется кнопка Use Video, кнопка Start скрыта.

    На верхней панели показана длительность видеозаписи – 00:25.
    После нажатия кнопки Play Video начнется просмотр отснятого видео с помощью AVPlayer.
    После нажатия кнопки Retake возвращаемся в первый режим.
    После нажатия кнопки Use Video у делегата видеосъемки срабатывает метод -(void)videoRecorderDidFinishRecording VideoWithOutputURL:(NSURL *)outputURL.

Три режима работы - экраны


В файле заголовка мне необходимо описать протокол делегата видеосъемки для обработки отмены видеозаписи и успешного завершения видеозаписи.

Вот так будет выглядеть файл заголовка VideoRecorderController.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVKit/AVKit.h>

@protocol VideoRecorderDelegate <NSObject>
// метод делегата, срабатывает при успешном завершении видеозаписи
- (void)videoRecorderDidFinishRecordingVideoWithOutputPath:(NSString *)outputPath;
// метод делегата, срабатывает при отмене видеозаписи
 - (void)videoRecorderDidCancelRecordingVideo;
@end

@interface VideoRecorderController : UIViewController
@property (nonatomic, retain) NSString *outputPath;		// путь к файлу видеозаписи
@property (nonatomic, assign) id<VideoRecorderDelegate> delegate;
@end


В файле реализации VideoRecorderController.m я задаю несколько констант для видеозаписи и описываю свойства и методы, которые нужно привязать в дизайнере интерфейса. Мне понадобятся также:

  • сессия захвата видео AVCaptureSession
  • файл видео выхода AVCaptureMovieFileOutput
  • устройство видео входа AVCaptureDeviceInput
  • слой для отображения видео в реальном времени AVCaptureVideoPreviewLayer
  • таймер и время для индикатора времени записи

Файл реализации VideoRecorderController.m - объявление переменных
#import "VideoRecorderController.h"

#define TOTAL_RECORDING_TIME    60*20	// максимальное время видеозаписи в секундах	
#define FRAMES_PER_SECOND       30		// количество кадров в секунду
#define FREE_DISK_SPACE_LIMIT   1024 * 1024	// минимальный размер свободного места (байт)
#define MAX_VIDEO_FILE_SIZE     160 * 1024 * 1024	// максимальный размер видеофайла (байт)
#define CAPTURE_SESSION_PRESET  AVCaptureSessionPreset352x288 // качество видеозаписи

#define BeginVideoRecording     1117	// звук начала записи видео
#define EndVideoRecording       1118	// звук конца записи видео

@interface VideoRecorderController () <AVCaptureFileOutputRecordingDelegate>
{
    BOOL WeAreRecording;	// флаг, определяющий идет ли запись видео
    
    AVCaptureSession *CaptureSession;		
    AVCaptureMovieFileOutput *MovieFileOutput;	
    AVCaptureDeviceInput *VideoInputDevice;	
}

// Эти элементы и методы нужно привязать в дизайнере интерфейса
@property (retain) IBOutlet UILabel *timeLabel; 	// индикатор времени записи на верхней панели
@property (retain) IBOutlet UIButton *startButton; 	// кнопка Start / Stop
@property (retain) IBOutlet UIImageView *circleImage;  // кружок вокруг кнопки Start
@property (retain) IBOutlet UIButton *cancelButton;      // кнопка Cancel
@property (retain) IBOutlet UIButton *useVideoButton; // кнопка Use Video
@property (retain) IBOutlet UIView *bottomView;	       // нижняя панель
@property (retain) IBOutlet UIButton *playVideoButton; // кнопка Play Video

- (IBAction)startStopButtonPressed:(id)sender;	 // обработчик нажатия кнопки Start / Stop
- (IBAction)cancel:(id)sender;			 // обработчик нажатия кнопки  Cancel
- (IBAction)useVideo:(id)sender;			// обработчик нажатия кнопки  Use Video
- (IBAction)playVideo:(id)sender;			// обработчик нажатия кнопки  Play Video

@property (retain) AVCaptureVideoPreviewLayer *PreviewLayer;

// таймер и время для индикатора времени записи
@property (retain) NSTimer *videoTimer;
@property (assign) NSTimeInterval elapsedTime;

@end


После того, как отработал метод viewDidLoad, необходимо выполнить следующие действия:

  • задать путь к файлу видеозаписи outputPath и удалить предыдущую запись
  • добавить обработчик на выход приложения в фон UIApplicationDidEnterBackgroundNotification
  • инициализировать сессию AVCaptureSession
  • найти видеоустройство по умолчанию AVCaptureDevice и создать устройство видео входа AVCaptureDeviceInput
  • перед тем, как добавить устройство видео входа, нужно обязательно вызвать
    метод [CaptureSession beginConfiguration]
  • затем добавить устройство видео входа в сессию AVCaptureSession
  • после добавления устройства видео входа нужно обязательно вызвать метод [CaptureSession commitConfiguration]
  • найти аудиоустройство по умолчанию AVCaptureDevice, создать устройство аудио входа AVCaptureDeviceInput и добавить это устройство в сессию AVCaptureSession
  • создать слой AVCaptureVideoPreviewLayer, на котором будет отображаться видео в реальном времени, привязать его к сессии AVCaptureSession, растянуть этот слой, на весь экран с сохранением пропорций (края кадра не попадут на экран)
  • пересчитать размеры слоя AVCaptureVideoPreviewLayer в зависимости от ориентации устройства и отправить этот слой на задний план, чтобы поверх него отображались все панели и кнопки управления
  • инициализировать видео выход в файл AVCaptureMovieFileOutput
  • задать частоту кадров в секунду, максимальную длину видео в секундах
  • задать максимальную длину видео в байтах
  • задать минимальный размер свободного места на диске в байтах
  • добавить видео выход в файл в сессию AVCaptureSession
  • задать качество видео для сессии AVCaptureSession
  • наконец выставить правильную ориентацию файла видео выхода AVCaptureMovieFileOutput и слоя просмотра видео AVCaptureVideoPreviewLayer

При переходе в другое приложение, если идет видеозапись, она останавится. После того, как отработал метод viewWillAppear, надо запустить сессию AVCaptureSession, на экране начинает отображаться видео в реальном времени. Но если произошел переход на этот экран после просмотра видео, то не нужно запускать AVCaptureSession — должна быть проверка, что нет файла видеозаписи.

Файл реализации VideoRecorderController.m - загрузка View Controller'а
@implementation VideoRecorderController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.outputPath = [[NSString alloc] initWithFormat:@"%@%@", NSTemporaryDirectory(), @"output.mov"];
    [self deleteVideoFile];
    [[NSNotificationCenter defaultCenter] addObserver: self
                                             selector: @selector(applicationDidEnterBackground:)
                                                 name: UIApplicationDidEnterBackgroundNotification
                                               object: nil];
    CaptureSession = [[AVCaptureSession alloc] init];
    AVCaptureDevice *VideoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    if (VideoDevice) {
        NSError *error = nil;
        VideoInputDevice = [AVCaptureDeviceInput deviceInputWithDevice:VideoDevice error:&error];
        if (!error) {
            [CaptureSession beginConfiguration];
            if ([CaptureSession canAddInput:VideoInputDevice]) {
                 [CaptureSession addInput:VideoInputDevice];
            }
            [CaptureSession commitConfiguration];
        }
    }
    AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
    NSError *error = nil;
    AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
    if (audioInput) {
        [CaptureSession addInput:audioInput];
    }
    [self setPreviewLayer:[[AVCaptureVideoPreviewLayer alloc] initWithSession:CaptureSession]];
    [self.PreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [self setupLayoutInRect:[[[self view] layer] bounds]];
    UIView *CameraView = [[UIView alloc] init];
    [[self view] addSubview:CameraView];
    [self.view sendSubviewToBack:CameraView];
    [[CameraView layer] addSublayer:self.PreviewLayer];
    MovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
    CMTime maxDuration = CMTimeMakeWithSeconds(TOTAL_RECORDING_TIME, FRAMES_PER_SECOND);
    MovieFileOutput.maxRecordedDuration = maxDuration;
    MovieFileOutput.maxRecordedFileSize = MAX_VIDEO_FILE_SIZE;
    MovieFileOutput.minFreeDiskSpaceLimit = FREE_DISK_SPACE_LIMIT;
    if ([CaptureSession canAddOutput:MovieFileOutput]) {
        [CaptureSession addOutput:MovieFileOutput];
    }
    if ([CaptureSession canSetSessionPreset:CAPTURE_SESSION_PRESET]) {
        [CaptureSession setSessionPreset:CAPTURE_SESSION_PRESET];
    }
    [self cameraSetOutputProperties];
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    if (WeAreRecording) {
        [self stopRecording];
    }
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (![[NSFileManager defaultManager] fileExistsAtPath:self.outputPath]) {
        WeAreRecording = NO;
        [CaptureSession startRunning];
    }
}


Поворот экрана разрешен только если сессия запущена, а запись еще не идет. Перед поворотом экрана необходимо пересчитать размеры экрана в зависимости от ориентации. После поворота надо выставить правильную ориентацию файла видео выхода AVCaptureMovieFileOutput и слоя просмотра видео AVCaptureVideoPreviewLayer.

Файл реализации VideoRecorderController.m - обработка поворотов
- (BOOL)shouldAutorotate {
    return (CaptureSession.isRunning && !WeAreRecording);
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscape | UIInterfaceOrientationMaskPortraitUpsideDown);
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [self setupLayoutInRect:CGRectMake(0, 0, size.width, size.height)];
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
    [coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {  
    } completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
        [self cameraSetOutputProperties];
    }];
}

// Этот метод выставляет правильную ориентацию файла видео выхода и слоя просмотра
// Он аналогичен viewWillTransitionToSize, нужен для поддержки версий iOS 7 и более ранних
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [self setupLayoutInRect:[[[self view] layer] bounds]];
    [self cameraSetOutputProperties];
}

// Пересчитываем размеры слоя просмотра в зависимости от ориентации устройства
- (void)setupLayoutInRect:(CGRect)layoutRect {
    [self.PreviewLayer setBounds:layoutRect];
    [self.PreviewLayer setPosition:CGPointMake(CGRectGetMidX(layoutRect),  CGRectGetMidY(layoutRect))];
}

// Выставляем правильную ориентацию файла видео выхода и слоя просмотра
- (void)cameraSetOutputProperties {
    AVCaptureConnection *videoConnection = nil;
    for ( AVCaptureConnection *connection in [MovieFileOutput connections] ) {
        for ( AVCaptureInputPort *port in [connection inputPorts] ) {
            if ( [[port mediaType] isEqual:AVMediaTypeVideo] ) {
                videoConnection = connection;
            }
        }
    }
    
    if ([videoConnection isVideoOrientationSupported]) {
        if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) {
            self.PreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortrait;
            [videoConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
        }
        else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown) {
            self.PreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationPortraitUpsideDown;
            [videoConnection setVideoOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
        }
        else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
            self.PreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeLeft;
            [videoConnection setVideoOrientation:AVCaptureVideoOrientationLandscapeLeft];
        }
        else {
            self.PreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
            [videoConnection setVideoOrientation:AVCaptureVideoOrientationLandscapeRight];
        }
    }
}


По нажатию кнопки Start / Stop начнется запись, если запись еще не идет. Если запись уже идет, то запись будет останавлена. По нажатию кнопки Cancel срабатывает метод делегата видеозаписи videoRecorderDidCancelRecordingVideo. По нажатию кнопки Retake сбрасывается таймер, меняются названия кнопок, скрывается кнопка Use Video, заново запускается сессия захвата видео. По нажатию кнопки Use Video срабатывает метод делегата видеозаписи videoRecorderDidFinishRecordingVideoWithOutputPath, в который необходимо передать путь к видео файлу. По нажатию кнопки Play Video начинается показ отснятого видео, используя AVPlayer. Когда срабатывает таймер видеозаписи, обновляется индикатор времени на верхней панели. Метод делегата файла видео записи срабатывает, если размер файла достиг максимально допустимого значения или время записи достигло максимально установленного. В этот момент запись останавливается.

Файл реализации VideoRecorderController.m - обработка нажатий кнопок, метод делегата AVCaptureFileOutputRecordingDelegate
- (IBAction)startStopButtonPressed:(id)sender {
    if (!WeAreRecording) {
        [self startRecording];
    }
    else {
        [self stopRecording];
    }
}

 - (IBAction)cancel:(id)sender {
    if ([CaptureSession isRunning]) {
        if (self.delegate) {
            [self.delegate videoRecorderDidCancelRecordingVideo];
        }
    }
    else {
        self.circleImage.hidden = NO;
        self.startButton.hidden = NO;
        self.useVideoButton.hidden = YES;
        [self.cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
        self.timeLabel.text = @"00:00";
        self.elapsedTime = 0;
        [CaptureSession startRunning];
    }
}

- (IBAction)useVideo:(id)sender {
    if (self.delegate) {
        [self.delegate videoRecorderDidFinishRecordingVideoWithOutputPath:self.outputPath];
    }
}

- (IBAction)playVideo:(id)sender {
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.outputPath]) {
        NSURL *outputFileURL = [[NSURL alloc] initFileURLWithPath:self.outputPath];
        AVPlayer *player = [AVPlayer playerWithURL:outputFileURL];
        AVPlayerViewController *controller = [[AVPlayerViewController alloc] init];
        [self presentViewController:controller animated:YES completion:nil];
        controller.player = player;
        controller.allowsPictureInPicturePlayback = NO;
        [player play];
    }
}

// Это начало записи видео
- (void)startRecording {
    // Проигрываем звук начала записи видео
    AudioServicesPlaySystemSound(BeginVideoRecording);
    WeAreRecording = YES;
    [self.cancelButton setHidden:YES];
    [self.bottomView setHidden:YES];
    [self.startButton setImage:[UIImage imageNamed:@"StopVideo"] forState:UIControlStateNormal];
    self.timeLabel.text = @"00:00";
    self.elapsedTime = 0;
    self.videoTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateTime) userInfo:nil repeats:YES];
    
    // Удаляем файл видеозаписи, если он существует, чтобы начать запись по новой
    [self deleteVideoFile];
    
    // Начинаем запись в файл видеозаписи
    NSURL *outputURL = [[NSURL alloc] initFileURLWithPath:self.outputPath];
    [MovieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
}

- (void)deleteVideoFile {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:self.outputPath]) {
        NSError *error = nil;
        if ([fileManager removeItemAtPath:self.outputPath error:&error] == NO) {
            // Обработчик ошибки удаления файла
        }
    }
}

// Это конец записи видео
- (void)stopRecording {
    // Проигрываем звук конца записи видео
    AudioServicesPlaySystemSound(EndVideoRecording);
    WeAreRecording = NO;
    [CaptureSession stopRunning];
    self.circleImage.hidden = YES;
    self.startButton.hidden = YES;
    [self.cancelButton setTitle:@"Retake" forState:UIControlStateNormal];
    [self.cancelButton setHidden:NO];
    [self.bottomView setHidden:NO];
    [self.startButton setImage:[UIImage imageNamed:@"StartVideo"] forState:UIControlStateNormal];
    // останавливаем таймер видеозаписи
    [self.videoTimer invalidate];
    self.videoTimer = nil;
    
    // Заканчиваем запись в файл видеозаписи
    [MovieFileOutput stopRecording];
}

- (void)updateTime {
    self.elapsedTime += self.videoTimer.timeInterval;
    NSInteger seconds = (NSInteger)self.elapsedTime % 60;
    NSInteger minutes = ((NSInteger)self.elapsedTime / 60) % 60;
    self.timeLabel.text = [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds];
}

- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
      fromConnections:(NSArray *)connections
                error:(NSError *)error {
    if (WeAreRecording) {
        [self stopRecording];
    }
    
    BOOL RecordedSuccessfully = YES;
    if ([error code] != noErr) {
        // Если при записи видео произошла ошибка, но файл был успешно сохранен,
        // будем все равно считать, что запись прошла успешно
        id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
        if (value != nil) {
            RecordedSuccessfully = [value boolValue];
        }
    }
    if (RecordedSuccessfully) {
        // Если запись прошла успешно, появляется кнопка Use Video
        self.useVideoButton.hidden = NO;
    }
}

- (void)viewDidUnload {
    [super viewDidUnload];
    CaptureSession = nil;
    MovieFileOutput = nil;
    VideoInputDevice = nil;
}
@end


Здесь можно найти исходный код моего проекта и попробовать, как работает приложение.

Ссылки на источники:

Автор: @ValentinStrazdin
Аркадия
рейтинг 38,11
Заказная разработка, IT-консалтинг

Комментарии (9)

  • 0
    Можно ли ускорить или замедлить скорость видео?
    Можно ли то же самое сделать на Swift 3.0?

    Заранее спасибо.
    Шарик
    • 0
      1. Скорость видео обычно определяется количеством кадров в секунду (чем больше кадров в секунду, тем меньше скорость). В моем случае Custom Video Recorder потребовался как раз для того, чтобы снимать видео с нормальной скоростью — 30 кадров в секунду даже если на телефоне включена настройка камеры — 60 fps.
      Новые модели позволяют снимать замедленное видео со скоростью до 240 кадров в секунду. В этом случае видео файл занимает очень много места. Мне нужно было уменьшить размер файла и при этом не слишком сильно ухудшить качество. Одним из возможных вариантов было сжатие видео перед отправкой. Другой вариант — создать свою камеру и задать параметры съемки, что я и сделал.
      Для того, чтобы изменить параметры записи в файл, мне достаточно было пары строчек кода:
      MovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
      CMTime maxDuration = CMTimeMakeWithSeconds(TOTAL_RECORDING_TIME, FRAMES_PER_SECOND);
      MovieFileOutput.maxRecordedDuration = maxDuration;
      Если вам нужно увеличить количество кадров в секунду, то кроме изменения константы FRAMES_PER_SECOND, потребуется еще настроить устройство видео входа. Более подробно можно посмотреть здесь — https://github.com/shu223/SlowMotionVideoRecorder

      2. Я не пробовал делать на Swift, но все ссылки на AVFoundation дают примеры кода на Swift. Так что, я думаю, на Swift тоже должно работать
      • 0
        Скорость видео обычно определяется количеством кадров в секунду


        Я про скорость воспроизведения видео, а не про fps, с которыми все понятно.
        • 0
          Скорость воспроизведения может принимать значения, отличные от 0 и 1, если для соответствующих AVPlayerItem свойства canPlaySlowForward, canPlayFastForward возвращают true.
          https://developer.apple.com/reference/avfoundation/avplayer/1388846-rate
          • +1
            Пардон, свойства плейера мне не нужны, у меня вопрос про вашу видеозапись. Упрощу диалог — можно записать видео 10 минутного заката солнца с длительностью 10 секунд?
            • 0
              Вы не можете изменить длительность записи видео, если запись идет напрямую в файл — AVCaptureMovieFileOutput. Вместо этого вы можете использовать AVCaptureStillImageOutput, запустить таймер и каждые 2 секунды делать снимок. За 10 минут у вас получится 300 кадров. После этого вам нужно вручную склеить эти кадры в видеоролик с помощью AVAssetWriter. Если использовать стандартные параметры — 30 кадров в секунду, то как раз получится 10 секунд видео.
      • 0
        Для замедленного воспроизведения отснятого видео нужно у объекта AVPlayer вместо метода -(void)play;
        вызвать метод -(void)setRate:(float)rate; Скорость воспроизведения может принимать значения от 0 до 1.
        0 — воспроизведение остановлено
        1 — воспроизведение с нормальной скоростью.

        Я добавил настройку устройства видео входа в своем проекте на GitHub — можно посмотреть как это выглядит на iPhone 6S, например.
        Достаточно поменять FRAMES_PER_SECOND 240, PLAYER_RATE 0.125f
  • 0
    Посмотрите в сторону GPUImage. Восхитительная библиотека, которая избавляет от рутинного кода и дает невероятные возможности.
    • 0
      Я несколько раз пытался добавить эту библиотеку в свои проекты. К сожалению, при всех своих достоинствах, оба раза она мне не подошла.
      Первый раз мне нужно было асинхронно обрабатывать изображения — это можно сделать только используя CPU, а данная библиотека делала все преобразования, используя графическую память GPU.
      Второй раз я хотел использовать камеру, но внешний вид GPUImageVideoCamera меня не устроил, а поменять его не было возможности.
      Один раз написав приложение, используя AVFoundation вместо UIImagePickerController, можно потом смело его использовать в других приложениях. При этом вы можете легко изменить внешний вид своей камеры.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка