这是我的传入数据的 flutter 代码,我得到的是一个 uint8list 数据流,看起来像这样(如果转录)
你好 你好,怎么样 你好,怎么样 你好,你好吗?
client.on(RealtimeEventType.conversationUpdated, (e) async {
final event = e as RealtimeEventConversationUpdated;
final item = event.result.item;
if (item?.formatted?.audio != null && item!.formatted!.audio.isNotEmpty) {
try {
await _trickleAudioStream?.playAudioStream(item.formatted!.audio, isFirstChunk);
if (isFirstChunk) {
isFirstChunk = false;
}
} catch (e) {
print("EXCEPTION: $e");
}
}
});
这是我的本机代码(用于我正在编写的插件)
import Flutter
import AVFoundation
import Foundation
struct CircularBuffer<Element> {
private var array: [Element?]
private var readIndex = 0
private var writeIndex = 0
private(set) var count = 0
private let capacity: Int
init(capacity: Int) {
self.capacity = capacity
self.array = Array(repeating: nil, count: capacity)
}
mutating func write(_ element: Element) {
array[writeIndex % capacity] = element
writeIndex += 1
if count < capacity {
count += 1
} else {
readIndex += 1 // Overwrite oldest if buffer is full
}
}
mutating func read() -> Element? {
guard count > 0 else { return nil }
let element = array[readIndex % capacity]
array[readIndex % capacity] = nil
readIndex += 1
count -= 1
return element
}
}
public class TrickleAudioStreamPlugin: NSObject, FlutterPlugin {
...
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "trickle_audio_stream", binaryMessenger: registrar.messenger())
let instance = TrickleAudioStreamPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "playAudioStream" {
guard let args = call.arguments as? [String: Any],
let pcmData = args["data"] as? FlutterStandardTypedData,
let isFirst = args["isFirst"] as? Bool else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "Invalid arguments", details: nil))
return
}
let data = pcmData.data
let timestamp = playAudioStream(convertPCM16ToFloat32(data), isFirst: isFirst)
result(timestamp)
} else if call.method == "stopAudioStream" {
stopAudioStream()
} else {
result(FlutterMethodNotImplemented)
}
}
func stopAudioStream() {
playerNode.stop()
isPlaying = false
}
func playAudioStream(_ floatPCMData: Data, isFirst: Bool) -> Double {
if isFirst {
resetBuffers()
stopAudioStream()
processedDataLength = 0
do {
try startAudioEngine()
playerNode.play()
isPlaying = true
} catch {
print("Error starting audio engine: \(error)")
return timeNow()
}
}
guard floatPCMData.count > processedDataLength else {
return timeNow()
}
let newData = floatPCMData.subdata(in: processedDataLength..<floatPCMData.count)
processedDataLength = floatPCMData.count
guard let buffer = createPCMBuffer(from: newData) else {
print("Failed to create PCM buffer")
return timeNow()
}
buffers.append(buffer)
currentSegmentDuration += Double(buffer.frameLength) / format.sampleRate
playerNode.scheduleBuffer(buffer) {
self.currentPlaybackPosition += Double(buffer.frameLength) / self.format.sampleRate
if let nextBuffer = self.buffers.first {
self.buffers.removeFirst()
self.playerNode.scheduleBuffer(nextBuffer) {
// Schedule next on completion
}
}
}
if isFirst {
playerNode.play()
isPlaying = true
}
return timeNow()
}
func startAudioEngine() throws {
if !audioEngine.isRunning {
try audioEngine.start()
}
}
private func createPCMBuffer(from floatPCMData: Data) -> AVAudioPCMBuffer? {
let frameCount = floatPCMData.count / MemoryLayout<Float>.size
guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
return nil
}
buffer.frameLength = buffer.frameCapacity
floatPCMData.withUnsafeBytes { floatBytes in
let floatBufferPointer = floatBytes.bindMemory(to: Float.self).baseAddress!
buffer.floatChannelData?.pointee.update(from: floatBufferPointer, count: frameCount)
}
return buffer
}
func resetBuffers() {
buffers.removeAll()
currentSegmentDuration = 0
currentPlaybackPosition = 0
playerNode.reset()
}
private func timeNow() -> Double {
return Date().timeIntervalSince1970 * 1000
}
private func convertPCM16ToFloat32(_ pcm16Data: Data) -> Data {
var floatData = Data(capacity: pcm16Data.count * MemoryLayout<Float>.size / MemoryLayout<Int16>.size)
pcm16Data.withUnsafeBytes { (int16Pointer: UnsafeRawBufferPointer) in
let int16Buffer = int16Pointer.bindMemory(to: Int16.self)
for sample in int16Buffer {
let floatSample = Float(sample) / Float(Int16.max)
floatData.append(contentsOf: withUnsafeBytes(of: floatSample) { Data($0) })
}
}
return floatData
}
}
但有时它会再次开始播放较旧的音频或其中的一小部分。
我希望数据能够无缝传输并播放音频,而不会重复以前的数据块。
根据您的问题,解决方法如下:
func playAudioStream(_ floatPCMData: Data, isFirst: Bool) -> Double {
if isFirst {
resetBuffers()
stopAudioStream()
processedDataLength = 0
try? startAudioEngine()
isPlaying = true
}
let buffer = createPCMBuffer(from: floatPCMData)
playerNode.scheduleBuffer(buffer) {
// Empty completion handler, don't reschedule
}
if !playerNode.isPlaying {
playerNode.play()
}
return timeNow()
}
这简化了缓冲区处理并删除了导致重复的重新安排逻辑。每个块只会播放一次。