from flask import Flask, request, send_file
import os
import wave
import audioop
app = Flask(__name__)
@app.route('/upload_audio', methods=['POST'])
def upload_audio():
audio_file = request.files.get('audio')
if audio_file:
file_path = os.path.join('uploads', audio_file.filename)
audio_file.save(file_path)
# 简单的音频压缩示例(这里只是简单调整采样率)
with wave.open(file_path, 'rb') as wf:
params = wf.getparams()
nchannels, sampwidth, framerate, nframes = params[:4]
frames = wf.readframes(nframes)
new_framerate = framerate // 2
new_audio = audioop.ratecv(frames, sampwidth, nchannels, framerate, new_framerate, None)[0]
new_file_path = os.path.join('compressed_uploads', audio_file.filename)
with wave.open(new_file_path, 'wb') as nf:
nf.setparams((nchannels, sampwidth, new_framerate, nframes, params[4], params[5]))
nf.writeframes(new_audio)
return '音频上传成功'
return '没有接收到音频文件'
@app.route('/download_audio/', methods=['GET'])
def download_audio(filename):
file_path = os.path.join('compressed_uploads', filename)
if os.path.exists(file_path):
return send_file(file_path, as_attachment=True)
return '文件不存在'
if __name__ == '__main__':
if not os.path.exists('uploads'):
os.makedirs('uploads')
if not os.path.exists('compressed_uploads'):
os.makedirs('compressed_uploads')
app.run(debug=True)
import android.Manifest
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private lateinit var mediaRecorder: MediaRecorder
private lateinit var mediaPlayer: MediaPlayer
private var isRecording = false
private var isPlaying = false
private val RECORD_REQUEST_CODE = 101
private val PLAY_REQUEST_CODE = 102
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recordButton: Button = findViewById(R.id.record_button)
val playButton: Button = findViewById(R.id.play_button)
recordButton.setOnClickListener {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
RECORD_REQUEST_CODE
)
} else {
startStopRecording()
}
}
playButton.setOnClickListener {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
PLAY_REQUEST_CODE
)
} else {
startStopPlaying()
}
}
}
private fun startStopRecording() {
if (!isRecording) {
mediaRecorder = MediaRecorder()
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
mediaRecorder.setOutputFile(filesDir.path + "/recording.3gp")
try {
mediaRecorder.prepare()
mediaRecorder.start()
isRecording = true
} catch (e: Exception) {
e.printStackTrace()
}
} else {
mediaRecorder.stop()
mediaRecorder.release()
isRecording = false
}
}
private fun startStopPlaying() {
if (!isPlaying) {
mediaPlayer = MediaPlayer()
try {
mediaPlayer.setDataSource(filesDir.path + "/recording.3gp")
mediaPlayer.prepare()
mediaPlayer.start()
isPlaying = true
} catch (e: Exception) {
e.printStackTrace()
}
} else {
mediaPlayer.stop()
mediaPlayer.release()
isPlaying = false
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == RECORD_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startStopRecording()
} else {
Toast.makeText(this, "录音权限未授予", Toast.LENGTH_SHORT).show()
}
} else if (requestCode == PLAY_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startStopPlaying()
} else {
Toast.makeText(this, "读取存储权限未授予", Toast.LENGTH_SHORT).show()
}
}
}
}
/upload_audio
路由处理音频文件的上传,接收到音频文件后保存到 uploads
目录,并进行简单的音频压缩(降低采样率)后保存到 compressed_uploads
目录。/download_audio/
路由用于下载压缩后的音频文件。有则开始或停止播放本地录制的音频文件。
startStopRecording
方法负责初始化 MediaRecorder
并开始或停止录制,录制的音频文件保存到应用的内部存储目录。startStopPlaying
方法负责初始化 MediaPlayer
并开始或停止播放指定路径的音频文件。onRequestPermissionsResult
方法用于处理权限请求的结果,根据权限是否授予来决定是否执行相应的录音或播放操作。val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", packageName, null)
intent.data = uri
startActivity(intent)
AMR_NB
和 3GP
格式。如果遇到兼容性问题,可以尝试在后端对音频进行格式转换,使用专业的音频处理库,如 pydub
(Python):from pydub import AudioSegment
def convert_audio_format(input_path, output_path, target_format):
audio = AudioSegment.from_file(input_path)
audio.export(output_path, format=target_format)
OkHttp
库进行网络请求,在 Python 后端使用 Flask - CORS
来处理跨域请求(如果需要),并对网络异常进行适当的捕获和处理。from aip import AipSpeech
APP_ID = 'your_app_id'
API_KEY = 'your_api_key'
SECRET_KEY = 'your_secret_key'
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
def synthesize_speech(text, lang='zh', output_file='output.mp3'):
result = client.synthesis(text, lang, 1, {
'vol': 5,
'per': 4
})
if not isinstance(result, dict):
with open(output_file, 'wb') as f:
f.write(result)
import com.iflytek.cloud.SpeechConstant
import com.iflytek.cloud.SpeechRecognizer
class SpeechRecognitionHelper(private val context: Context) {
private lateinit var speechRecognizer: SpeechRecognizer
init {
speechRecognizer = SpeechRecognizer.createRecognizer(context, null)
speechRecognizer.setParameter(SpeechConstant.LANGUAGE, "zh_cn")
speechRecognizer.setParameter(SpeechConstant.ACCENT, "mandarin")
}
fun startRecognition() {
speechRecognizer.startListening(object : RecognizerListener {
override fun onBeginOfSpeech() {
// 开始说话回调
}
override fun onEndOfSpeech() {
// 结束说话回调
}
override fun onResult(result: SpeechResult, isLast: Boolean) {
// 识别结果回调
val text = result
在上述 Android 代码中:
SpeechRecognitionHelper
类负责管理语音识别相关操作。在构造函数中初始化了 SpeechRecognizer
,并设置了识别语言为中文(简体中文,普通话口音)。startRecognition
方法开始语音识别,通过实现 RecognizerListener
接口来处理语音识别过程中的不同事件。
onBeginOfSpeech
回调在用户开始说话时触发,可以在这里进行一些提示操作,比如显示 “正在录音” 之类的 UI 提示。onEndOfSpeech
回调在用户停止说话时触发,此时可以关闭录音提示 UI 等操作。onResult
回调在识别出语音结果时触发,result
参数包含了识别出的文本信息,开发者可以根据需求进一步处理这些文本,例如显示在 UI 上或者进行语义分析等。于这是一个海外交友 APP,可能需要支持多种移动平台(如 iOS 和 Android)以及桌面平台(如 Windows、Mac)。
AVFoundation
框架进行音频录制和播放。对于语音合成和识别,可以集成苹果的 Speech
框架(iOS 10 及以上),或者使用第三方 SDK(如科大讯飞的 iOS SDK)。以下是一个简单的使用 AVFoundation
进行音频录制的示例代码(Objective - C):#import
@interface AudioRecorder : NSObject
@property (nonatomic, strong) AVAudioRecorder *recorder;
- (void)startRecording;
- (void)stopRecording;
@end
@implementation AudioRecorder
- (void)startRecording {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error;
[session setCategory:AVAudioSessionCategoryRecord error:&error];
if (error) {
NSLog(@"设置音频会话错误: %@", error);
return;
}
NSURL *url = [NSURL fileURLWithPath:@"/tmp/recording.m4a"];
NSDictionary *settings = @{
AVFormatIDKey : @(kAudioFormatMPEG4AAC),
AVSampleRateKey : @44100,
AVNumberOfChannelsKey : @1,
AVEncoderAudioQualityKey : @(AVAudioQualityHigh)
};
在上述 iOS 的 Objective - C 代码中:
AudioRecorder
类负责管理音频录制操作,它遵循 AVAudioRecorderDelegate
协议,该协议用于处理音频录制过程中的各种事件,比如录制完成、出现错误等。startRecording
方法用于开始音频录制:
AVAudioSession
,并设置其类别为录制模式。如果设置过程中出现错误,会在控制台打印错误信息并返回,不再继续录制操作。/tmp/recording.m4a
的 NSURL
对象,这里的路径可以根据实际需求进行调整。NSDictionary
。例如,设置音频格式为 kAudioFormatMPEG4AAC
(MPEG - 4 AAC 格式),采样率为 44100Hz,声道数为 1(单声道),音频质量为高。AVAudioRecorder
对象并开始录制。Info.plist
文件中添加相应的权限描述键值对。例如,对于音频录制权限,添加 NSMicrophoneUsageDescription
键,并设置一个描述字符串,向用户说明应用为什么需要使用麦克风权限。在代码中,在进行音频操作前检查权限状态:AVAudioSession *session = [AVAudioSession sharedInstance];
if (session.recordPermission == AVAudioSessionRecordPermissionDenied) {
// 权限被拒绝,引导用户到设置中开启权限
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"权限不足" message:@"应用需要麦克风权限才能录制音频,请在设置中开启。" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// 引导用户到设置页面
NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedInstance] canOpenURL:settingsURL]) {
[[UIApplication sharedInstance] openURL:settingsURL options:@{} completionHandler:nil];
}
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
} else if (session.recordPermission == AVAudioSessionRecordPermissionUndetermined) {
// 权限尚未确定,请求权限
[session requestRecordPermission:^(BOOL granted) {
if (granted) {
// 权限已授予,进行录制操作
[self startRecording];
} else {
// 权限被拒绝处理
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"权限被拒绝" message:@"应用无法录制音频,因为您拒绝了麦克风权限。" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
}];
} else {
// 权限已授予,直接进行录制操作
[self startRecording];
}
MPEG - 4 AAC
格式。在开发过程中,进行充分的设备和系统版本测试,确保音频在各种目标设备上都能正常录制和播放。如果遇到兼容性问题,可以尝试将音频转换为更通用的格式,iOS 提供了一些音频处理框架,如 AVAssetExportSession
可以用于音频格式转换:AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:ni
在上述使用 AVAssetExportSession
进行音频格式转换的代码片段中:
AVURLAsset
对象 asset
,它通过输入的音频文件 URL inputURL
来初始化,options
参数可以用来设置一些额外的选项,这里设为 nil
。这个 AVURLAsset
代表了要转换的音频资源。AVAssetExportSession
进行音频格式转换时,可能会由于各种原因导致转换失败,比如输入的音频文件损坏、目标格式不支持或者设备资源不足等。AVAssetExportSession
时,确保选择的目标格式是设备支持的。同时,在转换过程中捕获可能出现的错误,并进行相应的处理。可以通过监听 AVAssetExportSession
的 status
属性来获取转换状态:AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetAppleM4A];
exportSession.outputURL = outputURL;
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch (exportSession.status) {
case AVAssetExportSessionStatusCompleted:
NSLog(@"音频格式转换成功");
break;
case AVAssetExportSessionStatusFailed:
NSLog(@"音频格式转换失败: %@", exportSession.error);
// 在这里可以根据错误信息进行相应处理,例如提示用户转换失败原因
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"音频格式转换已取消");
break;
default:
break;
}
}];
Windows Multimedia API
进行音频录制和播放。对于语音合成,可以使用微软的 Speech API
,语音识别可以借助第三方库如 SpeechRecognition
(Python 结合相关 Windows 支持库)。以下是一个简单的使用 Windows Multimedia API
进行音频录制的 C++ 示例:#include
#include
#include
#pragma comment(lib, "winmm.lib")
int main() {
HWAVEIN hWaveIn;
WAVEFORMATEX wfx;
// 初始化波形格式
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 1;
wfx.nSamplesPerSec = 44100;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = (wfx.wBitsPerSample / 8) * wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
wfx.cbSize = 0;
// 打开波形输入设备
if (waveInOpen(&hWaveIn, WAVE_MAPPER, &wfx, 0, 0, WAVE_FORMAT_DIRECT) != MMSYSERR_NOERROR) {
std::cerr << "无法打开音频输入设备" << std::endl;
return 1;
}
// 开始录制(这里简化处理,实际应用需要更复杂的缓冲区管理等)
waveInStart(hWaveIn);
// 模拟录制一段时间
Sleep(5000);
// 停止录制
waveInStop(hWaveIn);
// 关闭波形输入设备
waveInClose(hWaveIn);
return 0;
}
在上述 C++ 代码中:
windows.h
、mmsystem.h
和 iostream
。windows.h
提供了 Windows 操作系统相关的常量和函数声明;mmsystem.h
用于多媒体相关操作,特别是音频操作;iostream
用于标准输入输出。#pragma comment(lib, "winmm.lib")
指令告诉编译器链接 winmm.lib
库,这个库包含了 Windows 多媒体 API 的实现。main
函数中:
HWAVEIN
类型的变量 hWaveIn
,用于表示波形输入设备句柄。WAVEFORMATEX
结构体 wfx
,这个结构体用于描述音频的格式信息。设置了音频格式标签为 WAVE_FORMAT_PCM
(脉冲编码调制格式),声道数为 1(单声道),采样率为 44100Hz,每个样本的位数为 16 位,计算了块对齐和每秒平均字节数,并将 cbSize
设置为 0。waveInOpen
函数打开波形输入设备,指定设备为 WAVE_MAPPER
(系统默认音频输入设备),传入音频格式结构体 wfx
。如果打开设备失败,输出错误信息并返回 1。waveInStart
函数开始音频录制,之后使用 Sleep
函数模拟录制 5 秒钟。waveInStop
函数停止录制,最后使用 waveInClose
函数关闭波形输入设备。waveInGetNumDevs
和 waveOutGetNumDevs
函数获取系统中可用的音频输入和输出设备数量,然后通过 waveInGetDevCaps
和 waveOutGetDevCaps
函数获取设备的详细信息,判断设备是否支持应用所需的音频格式和功能。如果遇到驱动问题,可以提示用户更新音频驱动程序,或者提供一些常见的驱动更新方法,如通过设备管理器更新驱动。在开发海外交友 APP 的语音功能时,需要充分考虑不同平台(Android、iOS 和桌面平台如 Windows)的特点和差异。在 Android 平台上,利用系统提供的音频和语音相关 API 实现语音录制、合成和识别功能,注意权限管理和多语言支持的细节。iOS 平台则依赖于其特定的框架,如 AVFoundation
和 Speech
框架,同时要处理好权限问题和音频格式兼容性。对于桌面平台,以 Windows 为例,借助 Windows Multimedia API
等工具进行音频操作,解决设备兼容性和性能优化等问题。通过对各个平台的深入了解和针对性的开发,能够为用户提供稳定、高质量的语音交互体验,增强海外交友 APP 的功能和用户吸引力。确保在整个开发过程中进行充分的测试,包括不同设备、系统版本和网络环境下的测试,以发现并解决可能出现的问题,最终打造出一个功能完善、用户体验良好的应用程序。