Flutter中如何实现视频文件的音频转录功能?
解决Flutter视频音频转录问题
你目前使用的speech_to_text包仅能实时捕获设备麦克风的音频输入,无法直接读取本地视频文件中的音频流,因此才会出现转录麦克风声音而非视频音频的情况。要实现视频音频转录,需要分两步处理:先提取视频中的独立音频文件,再对音频进行转写。
解决方案步骤
- 从视频文件中提取出单独的音频文件(推荐转成WAV格式,适配多数语音识别服务)
- 使用离线/在线语音转文字工具处理提取出的音频
推荐依赖包
- 音频提取:
ffmpeg_kit_flutter_full_gpl(专业的音视频处理工具,支持转码、提取等操作) - 离线转写:
vosk_flutter(轻量级离线语音识别库,支持多语言,无需网络) - 在线转写:可选用Google Cloud Speech-to-Text、Azure Speech等服务(需网络和API密钥)
具体代码实现
1. 添加依赖到pubspec.yaml
dependencies: flutter: sdk: flutter video_player: ^2.8.1 image_picker: ^1.0.4 ffmpeg_kit_flutter_full_gpl: ^5.1.0 vosk_flutter: ^0.3.0 assets: - assets/vosk-model-small-cn-0.22/ # 替换为你下载的Vosk模型路径
2. 实现视频音频提取函数
import 'dart:io'; import 'package:path_provider/path_provider.dart'; import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart'; Future<File?> extractAudioFromVideo(File videoFile) async { final tempDir = await getTemporaryDirectory(); final audioPath = '${tempDir.path}/extracted_audio.wav'; // 用FFmpeg提取音频,转成16kHz单声道WAV(Vosk要求的标准格式) final session = await FFmpegKit.execute( '-i "${videoFile.path}" -vn -acodec pcm_s16le -ar 16000 -ac 1 "$audioPath"', ); final returnCode = await session.getReturnCode(); if (returnCode?.isValueSuccess() == true) { return File(audioPath); } else { final log = await session.getAllLogsAsString(); print('音频提取失败: $log'); return null; } }
3. 修改页面逻辑实现完整功能
import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:image_picker/image_picker.dart'; import 'package:vosk_flutter/vosk_flutter.dart'; class VideoTranscriptionApp extends StatefulWidget { @override _VideoTranscriptionAppState createState() => _VideoTranscriptionAppState(); } class _VideoTranscriptionAppState extends State<VideoTranscriptionApp> { VideoPlayerController? _videoPlayerController; List<Map<String, dynamic>> _transcriptionData = []; bool _isTranscribing = false; @override void initState() { super.initState(); _initVoskModel(); } // 初始化Vosk语音识别模型 Future<void> _initVoskModel() async { await VoskFlutter.instance.loadModel('assets/vosk-model-small-cn-0.22/'); } Future<void> _pickVideoAndTranscribe() async { final pickedFile = await ImagePicker().pickVideo(source: ImageSource.gallery); if (pickedFile == null) return; setState(() => _isTranscribing = true); final videoFile = File(pickedFile.path); // 初始化视频播放器 _videoPlayerController = VideoPlayerController.file(videoFile) ..initialize().then((_) => setState(() {})); // 提取音频 final audioFile = await extractAudioFromVideo(videoFile); if (audioFile == null) { setState(() => _isTranscribing = false); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('音频提取失败')), ); return; } // 转录音频 await _transcribeAudio(audioFile); setState(() => _isTranscribing = false); } // 用Vosk处理音频转写,包含时间戳 Future<void> _transcribeAudio(File audioFile) async { final recognizer = await VoskFlutter.instance.createRecognizer(); _transcriptionData.clear(); await recognizer.processFile(audioFile.path, (result) { if (result.finalResult) { // 获取带精确时间戳的单词数据 result.wordTimestamps?.forEach((timestamp) { _transcriptionData.add({ 'word': timestamp.word, 'start': timestamp.start.toDouble(), 'end': timestamp.end.toDouble(), }); }); setState(() {}); } }); await recognizer.close(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('视频音频转录')), body: Column( children: [ if (_videoPlayerController?.value.isInitialized == true) AspectRatio( aspectRatio: _videoPlayerController!.value.aspectRatio, child: VideoPlayer(_videoPlayerController!), ), const SizedBox(height: 20), ElevatedButton( onPressed: _isTranscribing ? null : _pickVideoAndTranscribe, child: Text(_isTranscribing ? '转录中...' : '选择视频并转录'), ), const SizedBox(height: 20), Expanded( child: ListView.builder( itemCount: _transcriptionData.length, itemBuilder: (context, index) { final data = _transcriptionData[index]; return ListTile( title: Text(data['word']), subtitle: Text('开始: ${data['start'].toStringAsFixed(2)}s | 结束: ${data['end'].toStringAsFixed(2)}s'), ); }, ), ), ], ), ); } @override void dispose() { _videoPlayerController?.dispose(); super.dispose(); } }
注意事项
- Vosk模型需自行下载(可从Vosk官方仓库获取对应语言的轻量模型),并放置到项目assets目录
- FFmpeg包需要配置平台权限:
- Android:在
AndroidManifest.xml添加android.permission.READ_EXTERNAL_STORAGE、android.permission.WRITE_EXTERNAL_STORAGE - iOS:在
Info.plist添加NSPhotoLibraryUsageDescription权限描述
- Android:在
- 如果使用在线转写服务,可替换
_transcribeAudio函数为上传音频并调用对应API的逻辑
内容的提问来源于stack exchange,提问作者Mohammed Bekele
相关产品推荐
相关产品推荐

