iOS/Android 客户端开发同学如果想要开始学习音视频开发,最丝滑的方式是对音视频基础概念知识有一定了解后,再借助本地平台的音视频能力上手去实践音视频的采集 → 编码 → 封装 → 解封装 → 解码 → 渲染
过程,并借助音视频工具来分析和理解对应的音视频数据。
在音视频工程示例这个栏目,我们将通过拆解采集 → 编码 → 封装 → 解封装 → 解码 → 渲染
流程并实现 Demo 来向大家介绍如何在 iOS/Android 平台上手音视频开发。
这里是第三篇:iOS 音频封装 Demo。这个 Demo 里包含以下内容:
-
1)实现一个音频采集模块;
-
2)实现一个音频编码模块;
-
3)实现一个音频封装模块;
-
4)串联音频采集、编码、封装模块,将采集到的音频数据输入给 AAC 编码模块进行编码,再将编码后的数据输入给 M4A 封装模块封装和存储;
-
5)详尽的代码注释,帮你理解代码逻辑和原理。
1、音频采集模块
在这个 Demo 中,音频采集模块 KFAudioCapture
的实现与 《iOS 音频采集 Demo》 中一样,这里就不再重复介绍了,其接口如下:
KFAudioCapture.h
#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import "KFAudioConfig.h"
NS_ASSUME_NONNULL_BEGIN
@interface KFAudioCapture : NSObject
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(KFAudioConfig *)config;
@property (nonatomic, strong, readonly) KFAudioConfig *config;
@property (nonatomic, copy) void (^sampleBufferOutputCallBack)(CMSampleBufferRef sample); // 音频采集数据回调。
@property (nonatomic, copy) void (^errorCallBack)(NSError *error); // 音频采集错误回调。
- (void)startRunning; // 开始采集音频数据。
- (void)stopRunning; // 停止采集音频数据。
@end
NS_ASSUME_NONNULL_END
2、音频编码模块
同样的,音频编码模块 KFAudioEncoder
的实现与《iOS 音频编码 Demo》中一样,这里就不再重复介绍了,其接口如下:
#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
NS_ASSUME_NONNULL_BEGIN
@interface KFAudioEncoder : NSObject
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithAudioBitrate:(NSInteger)audioBitrate;
@property (nonatomic, assign, readonly) NSInteger audioBitrate; // 音频编码码率。
@property (nonatomic, copy) void (^sampleBufferOutputCallBack)(CMSampleBufferRef sample); // 音频编码数据回调。
@property (nonatomic, copy) void (^errorCallBack)(NSError *error); // 音频编码错误回调。
- (void)encodeSampleBuffer:(CMSampleBufferRef)buffer; // 编码。
@end
NS_ASSUME_NONNULL_END
3、音频封装模块
接下来,我们来实现一个音频封装模块,在这里输入编码后的数据,输出封装后的文件。
这次我们要封装的格式是 M4A,属于 MPEG-4 标准,通常普通的 MPEG-4 文件扩展名是 .mp4
,只包含音频的 MPEG-4 文件扩展名用 .m4a
。所以,其实我们这里实现的是一个 MP4 封装模块,支持将音频编码数据封装成 M4A,也支持将音视频数据封装成 MP4。关于 MP4 格式,可以看一看《MP4 格式》这篇文章了解一下。
由于 MP4 封装涉及到一些参数设置,所以我们先实现一个 KFMuxerConfig
类用于定义 MP4 封装的参数的配置。这里包括了:封装文件输出地址、封装文件类型、图像变换信息这几个参数。
KFMuxerConfig.h
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#import "KFMediaBase.h"
NS_ASSUME_NONNULL_BEGIN
@interface KFMuxerConfig : NSObject
@property (nonatomic, strong) NSURL *outputURL; // 封装文件输出地址。
@property (nonatomic, assign) KFMediaType muxerType; // 封装文件类型。
@property (nonatomic, assign) CGAffineTransform preferredTransform; // 图像的变换信息。比如:视频图像旋转。
@end
NS_ASSUME_NONNULL_END
KFMuxerConfig.m
#import "KFMuxerConfig.h"
@implementation KFMuxerConfig
- (instancetype)init {
self = [super init];
if (self) {
_muxerType = KFMediaAV;
_preferredTransform = CGAffineTransformIdentity;
}
return self;
}
@end
其中用到的 KFMediaType
是定义在 KFMediaBase.h
中的一个枚举:
KFMediaBase.h
#ifndef KFMediaBase_h
#define KFMediaBase_h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, KFMediaType) {
KFMediaNone = 0,
KFMediaAudio = 1 << 0, // 仅音频。
KFMediaVideo = 1 << 1, // 仅视频。
KFMediaAV = KFMediaAudio | KFMediaVideo, // 音视频都有。
};
#endif /* KFMediaBase_h */
接下来,我们来实现 KFMP4Muxer
模块。
KFMP4Muxer.h
#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
#import "KFMuxerConfig.h"
NS_ASSUME_NONNULL_BEGIN
@interface KFMP4Muxer : NSObject
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(KFMuxerConfig *)config;
@property (nonatomic, strong, readonly) KFMuxerConfig *config;
@property (nonatomic, copy) void (^errorCallBack)(NSError *error); // 封装错误回调。
- (void)startWriting; // 开始封装写入数据。
- (void)cancelWriting; // 取消封装写入数据。
- (void)appendSampleBuffer:(CMSampleBufferRef)sampleBuffer; // 添加封装数据。
- (void)stopWriting:(void (^)(BOOL success, NSError *error))completeHandler; // 停止封装写入数据。
@end
NS_ASSUME_NONNULL_END
上面是 KFMP4Muxer
的接口设计,除了初始化方法
,主要是有获取封装配置
以及封装错误回调
的接口,另外就是开始写入封装数据
、取消写入封装数据
、添加封装数据
、停止写入封装数据
的接口。
在上面的添加封装数据
接口中,我们使用的是依然 CMSampleBufferRef[1] 作为参数类型,再次体现了它作为 iOS 音视频处理 pipeline 中的流通货币
的通用性。关于这点,我们在《iOS 音频采集 Demo》和