49618


■環境  
Mac/High Sierra 10.13.6/swift4.2/xcode 10.0

iosアプリで音を取得して解析することを繰り返すアプリを作成しようと思い、  
<https://qiita.com/a_jike/items/68dd13879f9df5b2b7a2>  
上記urlサイトを参考にさせていただきました。  
そこのサイトに著者のGitHub上のコードが公開されておりました。下記のURLです。  
<https://github.com/atsushijike/AudioService>

上記ソースコードのwritePackets(inBuffer:
AudioQueueBufferRef)関数の中で、バッファのコピーがおこなわれていますが、そこでmaxPacketCountよりstartingPacketCount+numPacketsが大きい場合に、書き込んだら溢れてしまう

[code]

    n = startingPacketCount+numPackets-maxPacketCount
    
[/code]

の数だけ先頭要素を削除し、後ろに要素を新たにn個要素を追加したうえで書き込みを行いたいです。  
録音がずっと止まらないようにバッファ不足分だけ先頭をずらして後ろに足していくイメージです。大枠としての案は下記の２点かと思うのですが、その方法がわからず質問致しました。

■案１  
直接バッファの先頭を削除し、後ろに削除した分の要素を追加する  
何かの関数でバッファの特定部分を削除し、追加したい分だけ結合する？  
配列のイメージで書くと以下のようになります  
バッファの削除したい先頭の要素数がnの時

[code]

    for _ in 0 ..< n{
        b1.removeFirst()
        b1.append(書き込みたいデータ)
    }
    
[/code]

■案２  
バッファb2を用意し、元のバッファb1から先頭の不要部分を除いたところを先に書き込み、  
今回書き込みたい分をb2に追加してからb2をb1にコピー  
配列のイメージで書くと以下のようになります  
バッファの削除したい先頭の要素数がnの時

[code]

    for i in n ..< b1.count{
        b2[i-n] = b1[i]
    }
    for i in b1.count-n ..< b1.count{
        b2[i] = (書き込みたいデータ)
    }
    b1 = b2
    
[/code]


CC BY-SA 4.0
2018-10-22T06:58:32.830
49523
2018-10-24T12:24:57.473
25745
question
xcode swift4 ポインタ
バッファの先頭アドレスを変更したい(データを追加した分先頭を削除したい)


見てみたいと言うコメントをいただいたので、回答として示させてもらいます。300行近い長いコードですが、ほぼ全面的に修正しているので全行そのまま掲載します。

* * *

その前にざっとリングバッファの説明だけ。

[code]

    |<-                maxSampleCount              ->|
    |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
    
[/code]

現在入っているデータの先頭と、次に書き込むべきデータの位置を保持することにより、このバッファの一部だけを使用していることを記録しておきます。

[code]

    |<-                maxSampleCount              ->|
    |xxxxxxx|-     usingSampleCount     ->|xxxxxxxxxx|
            ↑
            startingSampleIndex
    
[/code]

(xxxxは未使用領域。)

空いている場所に書き込むだけなら、場所をずらさずに続きに書き込んでいきます。

[code]

    |<-                maxSampleCount              ->|
    |xxxxxxx|-     usingSampleCount       ---->|xxxxx|
            ↑
            startingSampleIndex
    
[/code]

(書き込んだ分`usingSampleCount`を増やす。)

もういらなくなったデータがあれば、前に詰めるのではなく、2つの情報をちょっと書き換えるだけです。

[code]

    |<-                maxSampleCount              ->|
    |xxxxxxxxxxxx|-usingSampleCount       ---->|xxxxx|
            -----↑
                 startingSampleIndex
    
[/code]

(`usingSampleCount`は減らす。`startingSampleIndex`は増やす。)

領域がバッファの終端を跨いだらどうなるのか、ちょっと面倒くさくなりますが、雰囲気は掴んでもらえると思います。

* * *

以上の内容をコードにしたのがこちら。原則として元の`AudioService`クラスと同じ構成になるようにしていますが、

  * プロパティ名やメソッド名を見直し
  * `NSData`を`Data`に変更
  * `buffer`を`UnsafeMutableRawPointer`から`UnsafeMutablePointer<Int16>`に

と、リングバッファ化と合わせ中身は大幅に変わっているので、クラス名も`AudioService`から`MyAudioService`に変更してあります。特に「余分なデータを削除してしまう」と言う処理の必要な`writeSamples(inBuffer:)`メソッドには少し細かめのコメントをつけておいたので確認してみてください。

[code]

    import Foundation
    import AudioToolbox
    
    extension Notification.Name {
        static let audioServiceDidUpdateData = Notification.Name(rawValue: "AudioServiceDidUpdateDataNotification")
    }
    
    func myAudioQueueInputCallback(inUserData: UnsafeMutableRawPointer?,
                                   inAQ: AudioQueueRef,
                                   inBuffer: AudioQueueBufferRef,
                                   inStartTime: UnsafePointer<AudioTimeStamp>,
                                   inNumberPacketDescriptions: UInt32,
                                   inPacketDescs: UnsafePointer<AudioStreamPacketDescription>?) {
        let audioService: MyAudioService = Unmanaged.fromOpaque(inUserData!).takeUnretainedValue()
        print(audioService)
        audioService.writeSamples(inBuffer: inBuffer)
        AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil);
    
        print("startingPacketCount: \(audioService.startingSampleIndex), maxPacketCount: \(audioService.maxSampleCount)")
        //「録音がずっと止まらないように」と言うことなので、ここでは録音の停止は判定しない
    }
    
    func myAudioQueueOutputCallback(inUserData: UnsafeMutableRawPointer?,
                                    inAQ: AudioQueueRef,
                                    inBuffer: AudioQueueBufferRef) {
        let audioService: MyAudioService = Unmanaged.fromOpaque(inUserData!).takeUnretainedValue()
        audioService.readSamples(inBuffer: inBuffer)
        AudioQueueEnqueueBuffer(inAQ, inBuffer, 0, nil)
    
        print("playingSampleCount: \(audioService.playingSampleCount), maxPacketCount: \(audioService.maxSampleCount)")
        if audioService.playingSampleCount == 0 {
            audioService.stop()
        }
    }
    
    class MyAudioService {
        let buffer: UnsafeMutablePointer<Int16>
        let maxSampleCount: Int
        //バッファ内の有効データの開始位置
        var startingSampleIndex: Int = 0
        //バッファ内の有効データの数
        var usingSampleCount: Int = 0
        //再生中の位置
        var playingSampleIndex: Int = 0
        //再生用の残りデータの数
        var playingSampleCount: Int = 0
    
        var audioQueueObject: AudioQueueRef?
    
        let numSamplesToRead: Int = 1024
        let numSamplesToWrite: Int = 1024
    
        let seconds: Int = 10 //`buffer`に格納可能な最大秒数
        let sampleRate: Int = 48_000 //サンプリングレート(Hz)
        let framesPerPacket: Int = 1 //Lenear PCM の場合1フレーム=1パケット
        let channelsPerFrame: Int = 1 //Monoral: 1フレーム辺りのチャンネル数は1(2ch Stereoなら2とする)
        let bytesPerSample: Int = MemoryLayout<Int16>.stride
    
        let inBufferCount = 3
        let outBufferCount = 3
    
        let audioFormat: AudioStreamBasicDescription
    
        var data: Data? {
            didSet {
                NotificationCenter.default.post(name: .audioServiceDidUpdateData, object: self)
            }
        }
    
        init() {
            let bytesPerPacket = bytesPerSample * framesPerPacket * channelsPerFrame
            audioFormat = AudioStreamBasicDescription(
                mSampleRate: Float64(sampleRate),
                mFormatID: kAudioFormatLinearPCM,
                mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked),
                mBytesPerPacket: UInt32(bytesPerPacket),
                mFramesPerPacket: UInt32(framesPerPacket),
                mBytesPerFrame: UInt32(bytesPerSample * channelsPerFrame),
                mChannelsPerFrame: UInt32(channelsPerFrame),
                mBitsPerChannel: UInt32(MemoryLayout<Int16>.size * 8),
                mReserved: 0
            )
            maxSampleCount = sampleRate * seconds
            //UnsafeMutablePointer使用の基本は
            //allocate -> initialize -> (使用) -> deinitialize -> deallocate のAIDDカルテット
            buffer = UnsafeMutablePointer.allocate(capacity: maxSampleCount)
            buffer.initialize(repeating: 0, count: maxSampleCount)
        }
    
        deinit {
            buffer.deinitialize(count: maxSampleCount)
            buffer.deallocate()
            if let audioQueueObject = audioQueueObject {
                AudioQueueStop(audioQueueObject, true)
                AudioQueueDispose(audioQueueObject, true)
                self.audioQueueObject = nil
            }
        }
    
        func startRecord() {
            print(#function)
            guard audioQueueObject == nil else  { return }
            data = nil
            prepareForRecord()
            let err: OSStatus = AudioQueueStart(audioQueueObject!, nil)
            print("err: \(err)")
        }
    
        func stopRecord() {
            print(#function, "usingSampleCount:", usingSampleCount)
            //領域が途中で途切れているかもしれないので、`bytesNoCopy`など連続した領域をあてにしたイニシャライザは使えない
            var data = Data(count: usingSampleCount * bytesPerSample)
            data.withUnsafeMutableBytes { (dataBytes: UnsafeMutablePointer<Int16>) in
                if startingSampleIndex + usingSampleCount <= maxSampleCount {
                    dataBytes.assign(from: buffer.advanced(by: startingSampleIndex), count: usingSampleCount)
                } else {
                    let firstReadCount = maxSampleCount - startingSampleIndex
                    dataBytes.assign(from: buffer.advanced(by: startingSampleIndex), count: firstReadCount)
                    dataBytes.advanced(by: firstReadCount).assign(from: buffer, count: usingSampleCount - firstReadCount)
                }
            }
            self.data = data
            guard let audioQueueObject = audioQueueObject else  { return }
            AudioQueueStop(audioQueueObject, true)
            AudioQueueDispose(audioQueueObject, true)
            self.audioQueueObject = nil
        }
    
        func play() {
            print(#function)
            guard audioQueueObject == nil else  { return }
            prepareForPlay()
            let err: OSStatus = AudioQueueStart(audioQueueObject!, nil)
            print("err: \(err)")
        }
    
        func stop() {
            print(#function)
            guard let audioQueueObject = audioQueueObject else  { return }
            AudioQueueStop(audioQueueObject, true)
            AudioQueueDispose(audioQueueObject, true)
            self.audioQueueObject = nil
        }
    
        func setData(_ data: Data) {
            self.data = data
            var writeLen = data.count/bytesPerSample
            if writeLen > maxSampleCount {writeLen = maxSampleCount}
            data.withUnsafeBytes{ (dataPtr: UnsafePointer<Int16>) in
                buffer.assign(from: dataPtr, count: writeLen)
            }
            startingSampleIndex = 0
            usingSampleCount = writeLen
        }
    
        private func prepareForRecord() {
            print(#function)
            var audioFormat = self.audioFormat
    
            startingSampleIndex = 0
            usingSampleCount = 0
    
            AudioQueueNewInput(&audioFormat,
                               myAudioQueueInputCallback,
                               Unmanaged.passUnretained(self).toOpaque(),
                               CFRunLoopGetCurrent(),
                               CFRunLoopMode.commonModes.rawValue,
                               0,
                               &audioQueueObject)
    
            let bufferByteSize = numSamplesToWrite * bytesPerSample
            for _ in 0..<inBufferCount {
                var buffer: AudioQueueBufferRef? = nil
                AudioQueueAllocateBuffer(audioQueueObject!, UInt32(bufferByteSize), &buffer)
                AudioQueueEnqueueBuffer(audioQueueObject!, buffer!, 0, nil)
            }
        }
    
        private func prepareForPlay() {
            print(#function)
            var audioFormat = self.audioFormat
    
            AudioQueueNewOutput(&audioFormat,
                                myAudioQueueOutputCallback,
                                Unmanaged.passUnretained(self).toOpaque(),
                                CFRunLoopGetCurrent(),
                                CFRunLoopMode.commonModes.rawValue,
                                0,
                                &audioQueueObject)
    
            playingSampleIndex = startingSampleIndex
            playingSampleCount = usingSampleCount
    
            let bufferByteSize = numSamplesToWrite * bytesPerSample
            for _ in 0..<outBufferCount {
                var buffer: AudioQueueBufferRef? = nil
                AudioQueueAllocateBuffer(audioQueueObject!, UInt32(bufferByteSize), &buffer)
                myAudioQueueOutputCallback(inUserData: Unmanaged.passUnretained(self).toOpaque(),
                                           inAQ: audioQueueObject!,
                                           inBuffer: buffer!)
            }
        }
    
        func readSamples(inBuffer: AudioQueueBufferRef) {
            print(#function)
            var numSamples = playingSampleCount
            if numSamples > numSamplesToRead {
                numSamples = numSamplesToRead
            }
    
            if numSamples > 0 {
                //outBuffer内のポインタをUnsafePointer<Int16>に変換
                let outSamples = inBuffer.pointee.mAudioData.assumingMemoryBound(to: Int16.self)
                if playingSampleIndex + numSamples <= maxSampleCount {
                    //連続して読み出せる場合
                    outSamples.assign(from: buffer.advanced(by: playingSampleIndex), count: numSamples)
                } else {
                    let firstReadCount = maxSampleCount - playingSampleIndex
                    outSamples.assign(from: buffer.advanced(by: playingSampleIndex), count: firstReadCount)
                    outSamples.advanced(by: firstReadCount).assign(from: buffer, count: numSamples - firstReadCount)
                }
                inBuffer.pointee.mAudioDataByteSize = UInt32(bytesPerSample * numSamples)
                inBuffer.pointee.mPacketDescriptionCount = UInt32(numSamples)
                playingSampleIndex += numSamples
                playingSampleIndex %= maxSampleCount
                playingSampleCount -= numSamples
            } else {
                inBuffer.pointee.mAudioDataByteSize = 0
                inBuffer.pointee.mPacketDescriptionCount = 0
            }
        }
    
        func writeSamples(inBuffer: AudioQueueBufferRef) {
            print(#function)
            print("mAudioDataByteSize: \(inBuffer.pointee.mAudioDataByteSize), numPackets: \(inBuffer.pointee.mAudioDataByteSize / 2)")
            //inBuffer内のポインタをUnsafePointer<Int16>に変換
            var inSamples = inBuffer.pointee.mAudioData.assumingMemoryBound(to: Int16.self)
            //inBuffer内の標本数を求める
            var numSamples = Int(inBuffer.pointee.mAudioDataByteSize) / bytesPerSample
            //はみ出す場合に古いデータを消す処理
            if numSamples > maxSampleCount {
                //inBuffer内の標本数がバッファの最大を超える場合、inBufferの後ろのデータを入るだけ詰め込む
                inSamples = inSamples.advanced(by: numSamples - maxSampleCount)
                numSamples = maxSampleCount
                //バッファの中身は空にする
                startingSampleIndex = 0
                usingSampleCount = 0
            } else if numSamples > maxSampleCount - usingSampleCount {
                //inBuffer内の標本数がバッファの空き容量を超える場合
                //バッファ内の古い要素は捨てる
                startingSampleIndex += numSamples - (maxSampleCount - usingSampleCount)
                usingSampleCount -= numSamples - (maxSampleCount - usingSampleCount)
                //末尾を超えたら先頭に回す
                startingSampleIndex %= maxSampleCount
            }
            //次に書き込むべき位置を求める
            let writingSampleIndex = (startingSampleIndex + usingSampleCount) % maxSampleCount
            if writingSampleIndex + numSamples <= maxSampleCount {
                //全データ一気に書き込めるなら一回で終了
                buffer.advanced(by: writingSampleIndex).assign(from: inSamples, count: numSamples)
            } else {
                //バッファの末尾までの収まる標本数を求める
                let firstWriteCount = maxSampleCount - writingSampleIndex
                //上で求めた標本数分だけwritingSampleIndexから書き込み
                buffer.advanced(by: writingSampleIndex).assign(from: inSamples, count: firstWriteCount)
                //残りを先頭に書き込む
                buffer.assign(from: inSamples.advanced(by: firstWriteCount), count: numSamples - firstWriteCount)
            }
            usingSampleCount += numSamples
        }
    }
    
[/code]

関連して、元プロジェクトの他の場所でも幾らかの修正が必要になります。また[別質問](https://ja.stackoverflow.com/a/49411/13972)のコメントに書かれていたように`NSData`からの標本データの読み取りが間違っているようなので、そちらも修正した方がいいでしょう。もうご自分で修正された版を持っていると思いますが、私が書くならこんな感じということで。

[code]

    import Cocoa
    
    final class MyWaveView: NSView {
        //Swiftなんで、`NSData`は使用せずに`Data`を使用する
        var data: Data? {
            didSet {
                needsDisplay = true
            }
        }
    
        override func draw(_ dirtyRect: NSRect) {
            NSColor.white.set()
            NSBezierPath(rect: bounds).fill()
            NSColor.darkGray.set()
            NSBezierPath(rect: bounds).stroke()
    
            guard let data = data else { return }
    
            let bezierPath = NSBezierPath()
            let length = data.count / MemoryLayout<Int16>.stride
            let unit = bounds.width / CGFloat(length)
            let mid = bounds.height / 2
            //`Data`内のデータへのポインタを`UnsafePointer<Int16>`型として取得する場合の書き方
            data.withUnsafeBytes { (samples: UnsafePointer<Int16>) in
                bezierPath.move(to: NSPoint(x: 0, y: mid))
                for index in 0 ..< length {
                    let normalizedSample = CGFloat(samples[index]) / CGFloat(Int16.max)
                    bezierPath.line(to: NSPoint(x: unit * CGFloat(index), y: normalizedSample * mid + mid))
                }
            }
            NSColor.black.set()
            bezierPath.lineWidth = 1
            bezierPath.stroke()
        }
    }
    
[/code]

書き換え部分がかなり大きくなったので、つまらないミスが入っているかもしれません。お気付きの点があればご質問ともどもお知らせください。

ちなみに解析処理はもう書かれているということですが、それとうまくつながるようになんてことは一切考えていません。ご自身のコードは今どのようなものになっているのでしょうか？参考サイトの元プロジェクトからは解析処理の追加などを含め、色々変更されているかと思います。ご質問を書かれるときに肝心なところはご質問そのものに含めていただいていると思いますが、思わぬ部分に影響が出ることもあります。出来ればご自身のリポジトリをGithub等に持たれて、ご質問本文には入れていないコードも見られるようにしてもらった方が良いかもしれません。


CC BY-SA 4.0
2018-10-24T12:24:57.473
49618
2018-10-24T12:24:57.473
13972
49523
answer