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)
    }
}