MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的AV Foundation音视频处理

2021-09-112.0k 阅读

AV Foundation 框架概述

AV Foundation 是 iOS 和 macOS 平台上强大的多媒体框架,它提供了一套丰富的类和接口,用于处理音频、视频的录制、编辑、播放等操作。Objective - C 作为苹果开发的主要语言之一,与 AV Foundation 紧密结合,为开发者提供了便捷且高效的音视频处理能力。

AV Foundation 的重要组件

  1. AVAsset:代表一个多媒体资源,它可以是本地的音视频文件,也可以是网络上的流媒体资源。例如,一个 MP4 视频文件就可以通过 AVAsset 来表示。通过 AVAsset,我们可以获取到资源的各种元数据,如视频的时长、分辨率,音频的采样率等。
  2. AVAssetTrackAVAsset 可以包含多个 AVAssetTrack,每个 AVAssetTrack 代表一种媒体类型,比如视频轨道、音频轨道。以一个电影文件为例,可能有一个视频轨道用于呈现画面,一个或多个音频轨道用于提供不同语言的声音。
  3. AVPlayer:负责播放 AVAsset 或其 AVAssetTrack。它提供了简单的控制接口,如播放、暂停、快进、快退等功能,并且可以通过添加观察者来监听播放状态的变化。
  4. AVCaptureSession:用于管理音频和视频的捕获。它可以配置输入源(如摄像头、麦克风)和输出目的地(如文件输出、实时预览),实现音视频的实时录制功能。

视频播放

创建 AVPlayer

在 Objective - C 中,创建一个 AVPlayer 实例非常简单。首先,我们需要获取一个 AVAsset 实例,然后使用该 AVAsset 创建 AVPlayerItem,最后用 AVPlayerItem 创建 AVPlayer

#import <AVFoundation/AVFoundation.h>

// 假设我们有一个本地视频文件路径
NSString *videoFilePath = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"mp4"];
NSURL *videoURL = [NSURL fileURLWithPath:videoFilePath];

AVAsset *asset = [AVAsset assetWithURL:videoURL];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];

视频播放控制

  1. 播放与暂停AVPlayer 提供了 playpause 方法来控制播放状态。
// 播放
[player play];

// 暂停
[player pause];
  1. 快进与快退:我们可以通过修改 AVPlayerItemcurrentTime 属性来实现快进和快退。currentTime 是一个 CMTime 类型,它表示当前播放的时间点。
// 快进 10 秒
CMTime currentTime = player.currentItem.currentTime;
CMTime newTime = CMTimeAdd(currentTime, CMTimeMake(10, 1));
[player.currentItem seekToTime:newTime];

// 快退 5 秒
currentTime = player.currentItem.currentTime;
newTime = CMTimeSubtract(currentTime, CMTimeMake(5, 1));
[player.currentItem seekToTime:newTime];

视频播放界面集成

通常,我们会将视频播放集成到视图中。AVPlayerLayer 是一个专门用于显示 AVPlayer 视频内容的 CALayer 子类。

AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:playerLayer];

音频播放

创建 AVAudioPlayer

与视频播放不同,音频播放可以使用 AVAudioPlayer 类,它专门用于播放音频文件。首先,我们需要准备音频文件的 URL,然后创建 AVAudioPlayer 实例。

#import <AVFoundation/AVFoundation.h>

// 假设我们有一个本地音频文件路径
NSString *audioFilePath = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"mp3"];
NSURL *audioURL = [NSURL fileURLWithPath:audioFilePath];

NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioURL error:&error];
if (error) {
    NSLog(@"音频播放初始化错误: %@", error);
} else {
    [audioPlayer prepareToPlay];
}

音频播放控制

  1. 播放与暂停:与 AVPlayer 类似,AVAudioPlayer 也有 playpause 方法。
// 播放
[audioPlayer play];

// 暂停
[audioPlayer pause];
  1. 音量控制AVAudioPlayer 提供了 volume 属性来控制音量大小,取值范围是 0.0(静音)到 1.0(最大音量)。
// 设置音量为 0.5
audioPlayer.volume = 0.5;

音视频录制

配置 AVCaptureSession

  1. 设置输入设备:首先需要获取音频和视频的输入设备,通常使用 AVCaptureDevice 类来表示设备。例如,要获取后置摄像头设备:
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
  1. 创建输入设备输入对象:使用获取到的设备创建 AVCaptureDeviceInput 对象。
NSError *error;
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (error) {
    NSLog(@"视频输入设备初始化错误: %@", error);
}

AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
if (error) {
    NSLog(@"音频输入设备初始化错误: %@", error);
}
  1. 创建 AVCaptureSession:并将输入设备添加到会话中。
AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
if ([captureSession canAddInput:videoInput]) {
    [captureSession addInput:videoInput];
}
if ([captureSession canAddInput:audioInput]) {
    [captureSession addInput:audioInput];
}

设置输出

  1. 文件输出:使用 AVCaptureMovieFileOutput 来将录制的音视频输出到文件。
AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([captureSession canAddOutput:movieFileOutput]) {
    [captureSession addOutput:movieFileOutput];
}
  1. 实时预览:为了实现实时预览,可以使用 AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
previewLayer.frame = self.view.bounds;
[self.view.layer addSublayer:previewLayer];

开始与停止录制

  1. 开始录制:在设置好 AVCaptureSession 和输出后,调用 startRunning 方法开始录制。对于文件输出,还需要指定输出文件的 URL。
// 假设我们有一个输出文件路径
NSString *outputFilePath = NSTemporaryDirectory();
outputFilePath = [outputFilePath stringByAppendingPathComponent:@"recordedVideo.mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:outputFilePath];

[movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
[captureSession startRunning];
  1. 停止录制:调用 stopRecording 方法停止录制。
[movieFileOutput stopRecording];

音视频编辑

裁剪视频

  1. 获取源视频的 AVAsset
NSString *sourceVideoPath = [[NSBundle mainBundle] pathForResource:@"sourceVideo" ofType:@"mp4"];
NSURL *sourceVideoURL = [NSURL fileURLWithPath:sourceVideoPath];
AVAsset *sourceAsset = [AVAsset assetWithURL:sourceVideoURL];
  1. 定义裁剪范围:使用 CMTime 来定义裁剪的起始时间和持续时间。
CMTime start = CMTimeMake(10, 1); // 从第 10 秒开始
CMTime duration = CMTimeMake(20, 1); // 持续 20 秒
CMTimeRange range = CMTimeRangeMake(start, duration);
  1. 创建 AVMutableComposition:并添加视频轨道。
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  1. 从源视频轨道中插入裁剪部分到新的轨道
AVAssetTrack *sourceVideoTrack = [[sourceAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
NSError *error;
[videoTrack insertTimeRange:range ofTrack:sourceVideoTrack atTime:kCMTimeZero error:&error];
if (error) {
    NSLog(@"插入视频轨道错误: %@", error);
}
  1. 导出裁剪后的视频:使用 AVAssetExportSession 进行导出。
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
NSString *outputPath = NSTemporaryDirectory();
outputPath = [outputPath stringByAppendingPathComponent:@"croppedVideo.mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
    switch (exportSession.status) {
        case AVAssetExportSessionStatusCompleted:
            NSLog(@"裁剪视频导出完成");
            break;
        case AVAssetExportSessionStatusFailed:
            NSLog(@"裁剪视频导出失败: %@", error);
            break;
        default:
            break;
    }
}];

合并音频与视频

  1. 获取音频和视频的 AVAsset
NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"video" ofType:@"mp4"];
NSURL *videoURL = [NSURL fileURLWithPath:videoPath];
AVAsset *videoAsset = [AVAsset assetWithURL:videoURL];

NSString *audioPath = [[NSBundle mainBundle] pathForResource:@"audio" ofType:@"m4a"];
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
AVAsset *audioAsset = [AVAsset assetWithURL:audioURL];
  1. 创建 AVMutableComposition:并分别添加音频和视频轨道。
AVMutableComposition *composition = [AVMutableComposition composition];

AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *sourceVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
NSError *error;
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceVideoTrack atTime:kCMTimeZero error:&error];
if (error) {
    NSLog(@"插入视频轨道错误: %@", error);
}

AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *sourceAudioTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, audioAsset.duration) ofTrack:sourceAudioTrack atTime:kCMTimeZero error:&error];
if (error) {
    NSLog(@"插入音频轨道错误: %@", error);
}
  1. 导出合并后的音视频:同样使用 AVAssetExportSession
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
NSString *outputPath = NSTemporaryDirectory();
outputPath = [outputPath stringByAppendingPathComponent:@"combinedVideo.mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
    switch (exportSession.status) {
        case AVAssetExportSessionStatusCompleted:
            NSLog(@"合并音视频导出完成");
            break;
        case AVAssetExportSessionStatusFailed:
            NSLog(@"合并音视频导出失败: %@", error);
            break;
        default:
            break;
    }
}];

处理音视频元数据

获取视频元数据

  1. 通过 AVAsset 获取通用元数据AVAsset 提供了 commonMetadata 属性,它返回一个 AVMetadataItem 数组,包含了视频的通用元数据,如标题、作者等。
NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"video" ofType:@"mp4"];
NSURL *videoURL = [NSURL fileURLWithPath:videoPath];
AVAsset *videoAsset = [AVAsset assetWithURL:videoURL];

NSArray<AVMetadataItem *> *commonMetadata = videoAsset.commonMetadata;
for (AVMetadataItem *item in commonMetadata) {
    NSLog(@"元数据键: %@, 值: %@", item.key, item.value);
}
  1. 获取视频特定元数据:对于视频分辨率等特定元数据,可以通过 AVAssetTrack 来获取。
AVAssetTrack *videoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CGSize naturalSize = videoTrack.naturalSize;
NSLog(@"视频分辨率: %.0fx%.0f", naturalSize.width, naturalSize.height);

获取音频元数据

  1. 通过 AVAsset 获取音频元数据:与视频类似,AVAsset 也可以获取音频的元数据。
NSString *audioPath = [[NSBundle mainBundle] pathForResource:@"audio" ofType:@"m4a"];
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
AVAsset *audioAsset = [AVAsset assetWithURL:audioURL];

NSArray<AVMetadataItem *> *audioCommonMetadata = audioAsset.commonMetadata;
for (AVMetadataItem *item in audioCommonMetadata) {
    NSLog(@"音频元数据键: %@, 值: %@", item.key, item.value);
}
  1. 获取音频特定元数据:例如,获取音频的采样率。
AVAssetTrack *audioTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
NSDictionary *formatDescriptions = audioTrack.formatDescriptions;
for (id desc in formatDescriptions) {
    if ([desc isKindOfClass:CMFormatDescriptionGetTypeID()]) {
        const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription((__bridge CMFormatDescriptionRef)desc);
        if (asbd) {
            float sampleRate = asbd->mSampleRate;
            NSLog(@"音频采样率: %f", sampleRate);
        }
    }
}

应对不同场景的优化

低功耗场景下的优化

  1. 减少不必要的处理:在低功耗场景下,如设备电量较低时,应避免复杂的音视频处理操作,如高清视频的实时编辑。可以降低视频处理的分辨率或帧率,以减少 CPU 和 GPU 的负载。
// 例如,在导出视频时选择较低的分辨率预设
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPreset640x480];
  1. 合理管理资源:及时释放不再使用的 AVAssetAVPlayer 等对象,避免内存泄漏。可以使用自动释放池来管理临时对象。
@autoreleasepool {
    // 创建和使用音视频相关对象
    // 操作完成后,对象会在自动释放池销毁时被释放
}

网络环境下的优化

  1. 自适应流媒体播放:在网络环境不稳定时,采用自适应流媒体播放技术,如 HLS(HTTP Live Streaming)。AV Foundation 对 HLS 有很好的支持,通过 AVPlayer 可以轻松播放 HLS 流。
NSURL *hlsURL = [NSURL URLWithString:@"http://example.com/stream.m3u8"];
AVAsset *hlsAsset = [AVAsset assetWithURL:hlsURL];
AVPlayerItem *hlsPlayerItem = [AVPlayerItem playerItemWithAsset:hlsAsset];
AVPlayer *hlsPlayer = [AVPlayer playerWithPlayerItem:hlsPlayerItem];
  1. 预加载与缓存:对于网络音视频资源,可以进行预加载和缓存,以减少播放卡顿。AVAssetResourceLoader 可以用于实现自定义的资源加载策略,包括缓存机制。
// 创建自定义的资源加载处理类
@interface CustomResourceLoader : NSObject <AVAssetResourceLoaderDelegate>
@end

@implementation CustomResourceLoader
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
    // 实现预加载和缓存逻辑
    return YES;
}
@end

// 在 AVPlayerItem 上设置资源加载委托
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
CustomResourceLoader *loader = [[CustomResourceLoader alloc] init];
[playerItem.resourceLoader setDelegate:loader queue:dispatch_get_main_queue()];

与其他框架的结合使用

与 Core Image 框架结合进行视频滤镜处理

  1. 获取视频的 AVAsset
NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"video" ofType:@"mp4"];
NSURL *videoURL = [NSURL fileURLWithPath:videoPath];
AVAsset *videoAsset = [AVAsset assetWithURL:videoURL];
  1. 创建 AVMutableComposition:并添加视频轨道。
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *sourceVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
NSError *error;
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceVideoTrack atTime:kCMTimeZero error:&error];
if (error) {
    NSLog(@"插入视频轨道错误: %@", error);
}
  1. 使用 Core Image 滤镜:例如,添加一个黑白滤镜。
#import <CoreImage/CoreImage.h>

CIFilter *blackAndWhiteFilter = [CIFilter filterWithName:@"CIColorControls"];
[blackAndWhiteFilter setDefaults];
[blackAndWhiteFilter setValue:@0.0 forKey:@"inputSaturation"];

AVVideoComposition *videoComposition = [AVVideoComposition videoCompositionWithAsset:composition applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
    CIImage *sourceImage = request.sourceImage;
    [blackAndWhiteFilter setValue:sourceImage forKey:kCIInputImageKey];
    CIImage *filteredImage = [blackAndWhiteFilter outputImage];
    [request finishWithImage:filteredImage context:nil];
}];
  1. 导出应用滤镜后的视频
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
NSString *outputPath = NSTemporaryDirectory();
outputPath = [outputPath stringByAppendingPathComponent:@"filteredVideo.mp4"];
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.videoComposition = videoComposition;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
    switch (exportSession.status) {
        case AVAssetExportSessionStatusCompleted:
            NSLog(@"应用滤镜视频导出完成");
            break;
        case AVAssetExportSessionStatusFailed:
            NSLog(@"应用滤镜视频导出失败: %@", error);
            break;
        default:
            break;
    }
}];

与 Core Audio 框架结合进行音频特效处理

  1. 获取音频的 AVAsset
NSString *audioPath = [[NSBundle mainBundle] pathForResource:@"audio" ofType:@"m4a"];
NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
AVAsset *audioAsset = [AVAsset assetWithURL:audioURL];
  1. 使用 Core Audio 进行特效处理:例如,添加一个回声特效。Core Audio 是一个底层音频处理框架,使用起来相对复杂,这里简单示意。
// 创建一个音频单元来处理回声效果
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Effect;
desc.componentSubType = kAudioUnitSubType_Echo;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;

AudioComponent component = AudioComponentFindNext(NULL, &desc);
AudioUnit audioUnit;
AudioComponentInstanceNew(component, &audioUnit);

// 设置回声参数
Float32 decay = 0.5;
Float32 delay = 0.2;
AudioUnitSetParameter(audioUnit, kEchoParam_DecayTime, kAudioUnitScope_Global, 0, decay, 0);
AudioUnitSetParameter(audioUnit, kEchoParam_DelayTime, kAudioUnitScope_Global, 0, delay, 0);
  1. 导出应用特效后的音频:这部分涉及到将处理后的音频数据重新封装成音频文件,过程较为复杂,需要使用 AudioFile 相关的 API 进行操作,这里暂不详细展开完整代码。

通过以上对 Objective - C 中 AV Foundation 音视频处理的详细介绍,从基础的播放、录制到复杂的编辑、元数据处理以及与其他框架的结合使用,相信开发者能够在实际项目中灵活运用这些知识,开发出高质量的音视频应用。在实际开发过程中,还需要根据具体的业务需求和设备特性,进一步优化和调整代码,以达到最佳的用户体验。