intdash Media SDK for Swift
動画や音声のエンコード、デコード、再生補助機能をサポートします。
■ このSDKを使用するために必要な開発環境
- Xcode 11.3以上
■ 動作環境
- iOS 11以上
■ 実装方法・機能サンプル:
・カメラ画像の取得
1. Info.plistに、カメラの使用に関する情報を追記する。
2. 利用するカメラデバイスを取得する。
3. 画像取得に関する設定を行う(解像度・FPS等)。
4. 画像データの出力に関する設定を行う(カラーフォーマット・向き・Delegate等)。
5. 画像データの取得を開始する。
6. 画像データの取得を停止する。
7. AVCaptureVideoDataOutputSampleBufferDelegateを利用した画像データの取得
8. SampleBufferManagerDelegateを利用した画像データの取得
// 1. Info.plistに、カメラの使用に関する情報を追記する。
Privacy - Camera Usage Description
var captureSession: AVCaptureSession?
var captureConnection: AVCaptureConnection?
var isFrontCamera: Bool = true
//var sampleManager: SampleBufferManager?
...
// 2. 利用するカメラデバイスを取得する。
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: isFrontCamera ? .front : .back) else {
print("Not Found Capture Device.")
return false
}
do {
// 3. 画像取得に関する設定を行う(解像度・FPS等)。
let input = try AVCaptureDeviceInput.init(device: device)
self.captureSession = AVCaptureSession()
self.captureSession!.sessionPreset = .hd1280x720
// self.captureSession!.sessionPreset = .inputPriority // プリセットを利用せずに細かくカスタマイズする場合は inputPriority を利用する
// Select Format
// ...
// Set FrameDuration(FrameRate)
// ...
// Shutter Speed
// ...
// Focus Mode
// ...
// etc...
// カメラデバイスに入力情報を追加
if self.captureSession!.canAddInput(input) {
self.captureSession!.addInput(input)
}
// 4. 画像データの出力に関する設定を行う(カラーフォーマット・向き・Delegate等)。
let output = AVCaptureVideoDataOutput()
// Pixel Format
let pixelFormat: Dictionary = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange]
output.videoSettings = pixelFormat
// キューがブロックされているときに新しいフレームが来たら削除するかどうか
output.alwaysDiscardsLateVideoFrames = true
// カメラデバイスに出力情報を追加
if self.captureSession!.canAddOutput(output) {
self.captureSession!.addOutput(output)
}
// 画像データを出力するコールバックの設定
output.setSampleBufferDelegate(self, queue: DispatchQueue.global()) // AVCaptureVideoDataOutputSampleBufferDelegate
// SampleBufferManagerを利用して、フレームレートを高く設定した状態のままダウンサンプリングすることも可能。
// self.sampleManager = SampleBufferManager.init(sourceSampleRate: Int(maxFrameRate), downSampleRate: frameRate)
// self.sampleManager?.delegate = self // SampleBufferManagerDelegate
// self.sampleManager?.attach(output: output, queue: DispatchQueue.global())
// 出力画像の向きを更新
for connection in output.connections {
for port in connection.inputPorts {
if port.mediaType == .video {
self.captureConnection = connection
}
}
}
// 以下の処理は、端末の向きが変化した場合にも必要となる可能性が高い
self.captureSession?.beginConfiguration()
let orientation = UIApplication.shared.statusBarOrientation
switch orientation {
case ...
self.captureSession?.videoOrientation = ...
}
self.captureSession?.commitConfiguration()
// 5. 画像データの取得を開始する。
self.captureSession!.startRunning()
} catch {
print("Can't Use Capture Device.")
}
...
// 6. 画像データの取得を停止する。
self.captureSession?.stopRunning()
self.captureSession = nil
...
// 7. AVCaptureVideoDataOutputSampleBufferDelegateを利用した画像データの取得
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
if output is AVCaptureVideoDataOutput {
// タイムスタンプの取得
let ts = sampleBuffer.videoTimeIntervalSince1970ForRTC
...
}
}
// 8. SampleBufferManagerDelegateを利用した画像データの取得
func captureVideoOutput(_ manager: SampleBufferManager, sampleBuffer: CMSampleBuffer, timestamp: MediaTimeStamp) {
// タイムスタンプの取得
let ts = timeStamp.rtc
...
}
・画像データのエンコード
1. エンコードオプションを設定する。
2. エンコーダーを作成する。
3. エンコード画像を出力するDelegateを設定する。
4. エンコード処理
5. エンコードされたデータの取得(IntdashVideoEncoderDelegate)
/// ビデオエンコーダー
var videoEncoder: IntdashVideoEncoder?
...
// 1. エンコードオプションを設定する。
var videoOption = IntdashVideoEncoderOption()
videoOption.frameRate = 30
videoOption.bitRate = 512000
videoOption.keyFrameIntervalTime = 2
...
// 2. エンコーダーを作成する。
self.videoEncoder?.dispose()
self.videoEncoder = IntdashVideoEncoder.init(codec: .H264, option: videoOption) // コーデックの指定を行う
// 3. エンコード画像を出力するDelegateを設定する。
self.videoEncoder?.delegate = self // IntdashVideoEncoderDelegate
...
// 4. エンコード処理
public func encodeImage(sampleBuffer: CMSampleBuffer, timestamp: TimeInterval) {
// Encode Sample Buffer
self.videoEncoder?.append(buffer: sampleBuffer, timestamp: timeStamp)
}
// 5. エンコードされたデータの取得(IntdashVideoEncoderDelegate)
func encodedFrame(_ encoder: IntdashVideoEncoder, frameData: Data, timestamp: TimeInterval) {
...
}
func encodeFailedSampleBuffer(_ encoder: IntdashVideoEncoder, error: NSError) {}
・画像データのデコード
1. デコードオプションを設定する。
2. デコーダーを作成する。
3. デコード画像を出力するDelegateを設定する。
4. デコード処理
5. デコードされたデータの取得(IntdashVideoDecoderDelegate)
/// ビデオデコーダー
var videoDecoder: IntdashVideoDecoder?
/// 描画するイメージビュー
@IBOutlet weak var decodeImageView: UIImageView!
...
// 1. デコードオプションを設定する。
var videoOption = IntdashVideoDecoderOption()
// 2. デコーダーを作成する。
self.videoDecoder?.dispose()
self.videoDecoder = IntdashVideoDecoder.init(codec: .H264, option: videoOption) // コーデックの指定を行う
// 3. デコード画像を出力するDelegateを設定する。
self.videoDecoder?.delegate = self // IntdashVideoDecoderDelegate
// 4. デコード処理
func downstreamManagerDidParseDataPoints(_ manager: DownstreamManager, streamId: Int, dataPoints: [RealtimeDataPoint]) { // IntdashClientDownstreamManagerDelegate
for dataPoint in dataPoints {
guard let data = dataPoint.data as? Data else { continue }
if measDataFeed.type == .h264 {
DispatchQueue.global().async {
// Decode Frame
self.videoDecoder?.append(frameData: data, timeStamp: measDataFeed.time)
}
}
}
...
}
// 5. デコードされたデータの取得(IntdashVideoDecoderDelegate)
func decodedFrameDataToUIImage(_ decoder: IntdashVideoDecoder, decodeImage: UIImage, timestamp: TimeInterval) {
DispatchQueue.main.async {
// Preview image.
self.decodeImageView.image = decodeImage
}
}
func decodedFrameData(_ decoder: IntdashVideoDecoder, decodeBuffer: CVImageBuffer, timestamp: TimeInterval) {}
func decodeFailedFrameData(_ decoder: IntdashVideoDecoder, error: NSError) {}
・音声データ取得
1. Info.plistに、マイクの使用に関する情報を追記する。
2. 音声キャプチャーセッションに関する設定を行う。
3. AudioCaptureSessionを初期化する。
4. 音声データを出力するDelegateを設定する。
5. 音声データの取得を開始する。
6. 音声データの取得を停止する。
7. AudioCaptureSessionDelegateを利用した音声データの取得
// 1. Info.plistに、マイクの使用に関する情報を追記する。
Privacy - Microphone Usage Description
/// 音声キャプチャーセッション
var audioCaptureSession: AudioCaptureSession?
...
// 2. 音声キャプチャーセッションに関する設定を行う。
do {
// デフォルトの設定を読み込む。(サンプリングレート=48kHz、音声フレームサイズ=1024)
try AVAudioSession.setupDefaultSettings() // 音声を扱う場合、この関数を `AppDelegate.didFinishLaunchingWithOptions()` 等で一度はコールすることを推奨。
// サンプルレートと、出力する1フレームあたりのサンプル数設定
let sampleRate: Double = 48000
let outputFrameSize = 1024
try AVAudioSession.setIOSampleRate(sampleRate: sampleRate, outputFrameSize: outputFrameSize)
// マイクの入力位置設定
let inputDataSourceOrientation: AVAudioSession.Orientation = AVAudioSession.Orientation(rawValue: "Back")
try AVAudioSession.setInputDataSourceOrientation(orientation: NSString(utf8String: inputDataSourceOrientation.rawValue))
...
} catch {
print("Failed to Setup Audio. \(error)")
}
// 3. AudioCaptureSessionを初期化する。
let audioSourceFormat: AVAudioCommonFormat = .pcmFormatFloat32
let sampleRate: Double = 48000
let audioSourceChannels = 1
guard let session = try? AudioCaptureSession(commonFormat: audioSourceFormat, sampleRate: sampleRate, channels: audioSourceChannels) else { return }
// 4. 音声データを出力するDelegateを設定する。
self.audioCaptureSession?.delegate = self // AudioCaptureSessionDelegate
// 5. 音声データの取得を開始する。
session.startRunning { (result) in
if result {
self.audioCaptureSession = session
}
}
...
// 6. 音声データの取得を停止する。
self.audioCaptureSession?.dispose()
self.audioCaptureSession = nil
...
// 7. AudioCaptureSessionDelegateを利用した音声データの取得
func captureOutput(_ session: AudioCaptureSession, didOutput sampleBuffer: CMSampleBuffer, timestamp: MediaTimeStamp) {
// タイムスタンプの取得
let timestamp = timestamp.rtc
...
}
・音声データの再生
1. AVAudioSessionをセットアップする。
2. PCMPlayerを初期化する。
3. 再生された音声を取得するDelegateを設定する(Optional)。
4. PCMPlayerを開始する。
5. PCMPlayerに音声データをセットする。
6. PCMPlayerを終了する。
/// 音声再生プレイヤー
var pcmPlayer: PCMPlayer?
...
// 1. AVAudioSessionをセットアップする。
do {
// デフォルトの設定を読み込む(サンプリングレート=48kHz、音声フレームサイズ=1024)
try AVAudioSession.setupDefaultSettings() // 音声を扱う場合、この関数を `AppDelegate.didFinishLaunchingWithOptions()` 等で一度はコールすることを推奨。
// サンプルレートと出力する1フレームあたりのサンプル数設定
let sampleRate: Double = 48000
let outputFrameSize = 1024
try AVAudioSession.setIOSampleRate(sampleRate: sampleRate, outputFrameSize: outputFrameSize)
...
// 音声の出力設定
let audioOutputPort: AVAudioSession.PortOverride = .speaker
try AVAudioSession.setOutputAudioPort(port: audioOutputPort)
} catch {
print("Failed to Setup Audio. \(error)")
}
...
// 2. PCMPlayerを初期化する。
let format: PCMFormat = PCMFormat(commonFormat: audioSourceFormat, channels: UInt16(audioSourceChannels), sampleRate: UInt32(sampleRate))
guard let player = try? PCMPlayer.init(pcmFormat: format, outputFrameSize: outputFrameSize) else {
print("Failed to Setup Audio Player.")
return
}
// ライブモードの設定
// ライブモードがオンの場合、再生待ちバッファが liveModeMaxBufferingTime の長さを超えると、
// バッファ長が liveModeMaxBufferingTime になるまでバッファの先頭がスキップされる。
// これにより、再生の遅延を抑制することができる。
player.isLiveMode = true
// ライブモード時の最大再生バッファ時間
let maxBufferingTime: TimeInterval = 0.3
player.liveModeMaxBufferingTime = maxBufferingTime
// 3. 再生された音声を取得するDelegateを設定する(Optional)。
player.delegate = self // PCMPlayerDelegate
// 4. PCMPlayerを開始する。
self.pcmPlayer = player
player.play { (result) in
if !result {
print("Failed to Start Audio Player.")
self.pcmPlayer = nil
}
}
...
// 5. PCMPlayerに音声データをセットする。
func captureOutput(_ session: AudioCaptureSession, didOutput sampleBuffer: CMSampleBuffer, timestamp: MediaTimeStamp) {
// タイムスタンプの取得
let timestamp = timestamp.rtc
...
if let samples = sampleBuffer.toPCMSamples() {
self.pcmPlayer?.append(sampleData: samples, time: timestamp)
}
}
...
// 6. PCMPlayerを終了する。
self.pcmPlayer?.dispose()
self.pcmPlayer = nil
func settedSamplesForAudioPlayQueue(_ player: PCMPlayer, sample: IntdashAudioSample) {}
func settedBlankSamplesForAudioPlayerQueue(_ player: PCMPlayer, sample: IntdashAudioSample) {}
・音声データのエンコード
1. エンコードオプションを設定する。
2. エンコーダーを作成する。
3. エンコードされた画像を出力するDelegateを設定する。
4. エンコード処理
5. エンコードされたデータの取得(IntdashAudioEncoderDelegate)
// 1. エンコードオプションを設定する。
var audioOption = IntdashAudioEncoderOption()
audioOption.sampleRate = sampleRate
audioOption.bitRate = 128000
// 2. エンコーダーを作成する。
self.audioEncoder = IntdashAudioEncoder.init(codec: .AAC, option: audioOption)
// 3. エンコードされた画像を出力するDelegateを設定する。
self.audioEncoder?.delegate = self // IntdashAudioEncoderDelegate
...
// 4. エンコード処理
public func encodeAudio(sampleBuffer: CMSampleBuffer, timestamp: TimeInterval) {
// Encode Sample Buffer
self.audioEncoder?.append(buffer: sampleBuffer, timestamp: timestamp)
}
// 5. エンコードされたデータの取得(IntdashAudioEncoderDelegate)
func encodedSamples(_ encoder: IntdashAudioEncoder, sampleData: Data, timestamp: TimeInterval) {
...
}
func encodeFailedSampleBuffer(_ encoder: IntdashAudioEncoder, error: NSError) {}
・音声データのデコード
1. デコードオプションを設定する。
2. デコーダーを作成する。
3. デコード対象の音声フォーマットを取得するDelegateを設定する(Optional)。
4. デコード画像を出力するDelegateを設定する。
5. デコード処理
6. デコードされたデータの取得(IntdashVideoDecoderDelegate)
7. デコード対象の音声フォーマットの取得(Optional)
/// 音声デコーダー
var audioDecoder: IntdashAudioDecoder?
...
// 1. デコードオプションを設定する。
var audioOption = IntdashAudioDecoderOption()
audioOption.decodeSampleRate = 48000
if let decodeFormat = IntdashAudioFormat.from(commonFormat: .pcmFormatFloat32) {
audioOption.decodeFormat = decodeFormat
}
let audioType: AudioType = .aac
switch audioType {
case .aac:
// 2. デコーダーを作成する。
self.audioDecoder?.dispose()
self.audioDecoder = IntdashAudioDecoder(codec: .AAC, option: audioOption)
// 3. デコード対象の音声フォーマットを取得するDelegateを設定する(Optional)。
self.audioDecoder?.aacDecoder?.sampleParseDelegate = self // AACDecoderSampleParseDelegate
...
}
// 4. デコード画像を出力するDelegateを設定する。
self.audioDecoder?.delegate = self // IntdashAudioDecoderDelegate
...
// 5. デコード処理
func downstreamManagerDidParseDataPoints(_ manager: DownstreamManager, streamId: Int, dataPoints: [RealtimeDataPoint]) { // IntdashClientDownstreamManagerDelegate
for dataPoint in dataPoints {
guard let data = dataPoint.data as? Data else { continue }
if dataPoint.dataModel.dataType == .aac {
DispatchQueue.global().async {
// Decode Frame
self.audioDecoder?.append(sampleData: data, timestamp: measDataFeed.time)
}
}
}
...
}
...
// 6. デコードされたデータの取得(IntdashVideoDecoderDelegate)
func decodedSamples(_ decoder: IntdashAudioDecoder, sampleData: Data, timestamp: TimeInterval) {
DispatchQueue.global().async {
// Play audio.
self.pcmPlayer?.append(sampleData: sampleData, time: timestamp)
}
}
func decodeFailedSampleData(_ decoder: IntdashAudioDecoder, error: NSError) {}
...
// 7. デコード対象の音声フォーマットの取得(Optional)
// AACDecoderSampleParseDelegate
func parsedAACHeader(_ decoder: AACDecoder, sampleRate: Double, channels: Int) {
// ※基本的には、初めて音声データのヘッダが解析された際に呼び出される
if let player = self.pcmPlayer {
self.pcmPlayer = nil
player.stop()
}
if self.pcmPlayer == nil {
// Start Audio Player
let pcmFormat = PCMFormat(commonFormat: decoder.decodeFormat.commonFormat, channels: UInt16(channels), sampleRate: UInt32(decoder.decodeSampleRate))
self.setupAudioPlayer(format: pcmFormat)
}
}