当前位置:   article > 正文

iOS平台语音边录边压边上传的实现_能够实现边上传边压缩吗

能够实现边上传边压缩吗


本文只讲述iOS平台如何实现边录制音频边压缩编码边上传到服务端,至于播放则只下载解码播放并不涉及


录制

使用AudioToolbox.framework -> AudioQueue类进行录音/播放


三个缓冲器Buffers:每个缓冲器都是一个存储音频数据的临时仓库。

一个缓冲队列Buffer Queue:一个包含音频缓冲器的有序队列。

一个回调Callback:一个自定义的队列回调函数。




  1. - (void)_createAudioInputQueue
  2. {
  3. APMM_DEBUG(@"WAudioInputQueue _createAudioInputQueue");
  4. if (![self _checkAudioQueueSuccess:AudioQueueNewInput(&_format, MCAudioQueueInuputCallback, (__bridge void *)(self), CFRunLoopGetMain(), NULL, 0, &_audioQueue)])
  5. {
  6. return;
  7. }
  8. AudioQueueAddPropertyListener(_audioQueue, kAudioQueueProperty_IsRunning, WAudioInputQueuePropertyCallback, (__bridge void *)(self));
  9. _meterState = (AudioQueueLevelMeterState *)calloc(sizeof(AudioQueueLevelMeterState),_format.mChannelsPerFrame);
  10. UInt32 trueValue = true;
  11. AudioQueueSetProperty(_audioQueue,kAudioQueueProperty_EnableLevelMetering, &trueValue, sizeof(UInt32));
  12. for (int i = 0; i < MCAudioQueueBufferCount; ++i)
  13. {
  14. AudioQueueBufferRef buffer;
  15. if (![self _checkAudioQueueSuccess:AudioQueueAllocateBuffer(_audioQueue, _bufferSize, &buffer)])
  16. {
  17. break;
  18. }
  19. if (![self _checkAudioQueueSuccess:AudioQueueEnqueueBuffer(_audioQueue, buffer, 0, NULL)])
  20. {
  21. break;
  22. }
  23. }
  24. }


  1. static void MCAudioQueueInuputCallback(void *inClientData,
  2. AudioQueueRef inAQ,
  3. AudioQueueBufferRef inBuffer,
  4. const AudioTimeStamp *inStartTime,
  5. UInt32 inNumberPacketDescriptions,
  6. const AudioStreamPacketDescription *inPacketDescs)
  7. {
  8. WAudioInputQueue *audioOutputQueue = (__bridge WAudioInputQueue *)inClientData;
  9. [audioOutputQueue handleAudioQueueOutputCallBack:inAQ
  10. buffer:inBuffer
  11. inStartTime:inStartTime
  12. inNumberPacketDescriptions:inNumberPacketDescriptions
  13. inPacketDescs:inPacketDescs];
  14. }
  15. - (void)handleAudioQueueOutputCallBack:(AudioQueueRef)audioQueue
  16. buffer:(AudioQueueBufferRef)buffer
  17. inStartTime:(const AudioTimeStamp *)inStartTime
  18. inNumberPacketDescriptions:(UInt32)inNumberPacketDescriptions
  19. inPacketDescs:(const AudioStreamPacketDescription *)inPacketDescs
  20. {
  21. if (_started)
  22. {
  23. [_buffer appendBytes:buffer->mAudioData length:buffer->mAudioDataByteSize];
  24. if ([_buffer length] >= _bufferSize)
  25. {
  26. NSRange range = NSMakeRange(0, _bufferSize);
  27. NSData *subData = [_buffer subdataWithRange:range];
  28. [_delegate inputQueue:self inputData:subData numberOfPackets:inNumberPacketDescriptions finish:NO];
  29. [_buffer replaceBytesInRange:range withBytes:NULL length:0];
  30. }
  31. [self _checkAudioQueueSuccess:AudioQueueEnqueueBuffer(_audioQueue, buffer, 0, NULL)];
  32. }else{
  33. [_buffer appendBytes:buffer->mAudioData length:buffer->mAudioDataByteSize];
  34. NSRange range = NSMakeRange(0, buffer->mAudioDataByteSize);
  35. NSData *subData = [_buffer subdataWithRange:range];
  36. [_delegate inputQueue:self inputData:subData numberOfPackets:inNumberPacketDescriptions finish:NO];
  37. [_buffer replaceBytesInRange:range withBytes:NULL length:0];
  38. }
  39. APMM_DEBUG(@"handleAudioQueueOutputCallBack, data length:%u",(unsigned int)buffer->mAudioDataByteSize);
  40. }

通过 AudioQueue类的注册的callback方法拿到语音buffer(PCM 未压缩音频数据)


压缩


目前语音压缩的编码格式很多,像AMR、SILK等,但是在同等采样率和比特率的条件下,SILK在音质包括降噪等方面脚AMR较优秀,可以看到微信也是采用后者。




silk相关参数的配置

  1. /* Define decode codec specific settings should be moved to h file */
  2. #define DECODE_MAX_BYTES_PER_FRAME 1024
  3. #define DECODE_MAX_INPUT_FRAMES 5
  4. #define DECODE_MAX_FRAME_LENGTH 480
  5. #define DECODE_FRAME_LENGTH_MS 20
  6. #define DECODE_MAX_API_FS_KHZ 48
  7. #define DECODE_MAX_LBRR_DELAY 2
  8. /* Define encode codec specific settings */
  9. #define ENCODE_MAX_BYTES_PER_FRAME 250 // Equals peak bitrate of 100 kbps
  10. #define ENCODE_MAX_INPUT_FRAMES 5
  11. #define ENCODE_FRAME_LENGTH_MS 20
  12. #define ENCODE_MAX_API_FS_KHZ 48

在录制中拿到的语音buffer 通过SILK的如上配置进行编码最终拿到编码后的silk数据buffer,到此,我们已经能实时拿到录音的编码后数据。



上传

如果按照其他类型文件上传,语音需要拿最终结束录音后保存的文件进行上传,每次用户录音完成,如果录音时间较久,比如60秒后才进行上传,那么意味着60秒后才去建立连接上传服务端,那么其在数据网络或者弱网条件下的耗时是很大的,于是我们可以在用户点击录音开始便可以建连、每次通过AudioQueue拿到编码后的数据进行写数据,直到用户结束录音,我们的录音数据也同时上传完毕。




这里我们可以使用

HTTP/1.1  POST

       multipart/form-data

首先,${bound} 是一个占位符,代表我们规定的分割符,可以自己任意规定,但为了避免和正常文本重复了,尽量要使用复杂一点的内容。

然后,Content-Type里指明了数据是以mutipart/form-data来编码,本次请求的boundary是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以–boundary开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以–boundary–标示结束。


POST http://www.example.com HTTP/1.1

Content-Type:multipart/form-data; boundary=${bound}

 

--${bound}

Content-Disposition: form-data; name="text"

 

title


--${bound}

Content-Disposition: form-data; name="file"; filename="chrome.png"

Content-Type: image/png

 

PNG ... content of chrome.png ...

--${bound}--


在iOS平台, 已经有很多优秀的网络开源库,这里我采用的是AFNetWorking的第三方开源库,它本身就已经支持了 multipart/form-data的协议封装,我们只需稍加改造,便可以通过HOLD住上传流来进行多表单方式的上传,一边录制一边压缩一边上传语音,直到结束录音没有语音数据。


  1. - (NSMutableURLRequest *)multipartFormStreamRequestWithMethod:(NSString *)method
  2. URLString:(NSString *)URLString
  3. parameters:(NSDictionary *)parameters
  4. constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
  5. error:(NSError *__autoreleasing *)error
  6. {
  7. NSParameterAssert(method);
  8. NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
  9. NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
  10. __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
  11. if (parameters) {
  12. for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
  13. NSData *data = nil;
  14. if ([pair.value isKindOfClass:[NSData class]]) {
  15. data = pair.value;
  16. } else if ([pair.value isEqual:[NSNull null]]) {
  17. data = [NSData data];
  18. } else {
  19. data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
  20. }
  21. if (data) {
  22. [formData appendPartWithFormData:data name:[pair.field description]];
  23. }
  24. }
  25. }
  26. if (block) {
  27. block(formData);
  28. }
  29. return [formData requestByFinalizingMultipartFormDataWithOutLength];
  30. }

  1. - (NSMutableURLRequest *)requestByFinalizingMultipartFormDataWithOutLength {
  2. if ([self.bodyStream isEmpty]) {
  3. return self.request;
  4. }
  5. // Reset the initial and final boundaries to ensure correct Content-Length
  6. [self.bodyStream setInitialAndFinalBoundaries];
  7. [self.request setHTTPBodyStream:self.bodyStream];
  8. [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
  9. [self.request setValue:@"100-Continue" forHTTPHeaderField:@"Expect"];
  10. return self.request;
  11. }

  1. - (NSInteger)read:(uint8_t *)buffer
  2. maxLength:(NSUInteger)length
  3. {
  4. if ([self streamStatus] == NSStreamStatusClosed) {
  5. return 0;
  6. }
  7. NSInteger totalNumberOfBytesRead = 0;
  8. #pragma clang diagnostic push
  9. #pragma clang diagnostic ignored "-Wgnu"
  10. while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
  11. if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
  12. if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
  13. break;
  14. }
  15. } else {
  16. NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
  17. NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
  18. if (numberOfBytesRead == -1) {
  19. self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
  20. break;
  21. }else if (numberOfBytesRead == 0){
  22. [NSThread sleepForTimeInterval:1];
  23. }else {
  24. totalNumberOfBytesRead += numberOfBytesRead;
  25. if (self.delay > 0.0f) {
  26. [NSThread sleepForTimeInterval:self.delay];
  27. }
  28. return totalNumberOfBytesRead;
  29. }
  30. }
  31. }
  32. #pragma clang diagnostic pop
  33. return totalNumberOfBytesRead;
  34. }










声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/418996
推荐阅读
相关标签
  

闽ICP备14008679号