You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将麦克风Uint8List原始流输入FFmpeg并转存/推流至RTSP?

正确实现方案

你的核心问题在于:直接将Uint8List对象作为FFmpeg的输入路径,FFmpeg无法识别原始PCM数据流;同时每次接收音频片段就启动一个FFmpeg进程,既低效也无法实现连续的流处理。下面是基于mic_stream和ffmpeg_kit_flutter的正确实现方式,分为本地保存和RTSP推送两种场景。

前置说明

从mic_stream获取的是16位深度、双声道、44100采样率的原始PCM数据,必须在FFmpeg命令中明确指定这些参数,让FFmpeg正确解析输入流。


场景1:实时保存为MP3文件

需要启动一个持续的FFmpeg进程,将麦克风输入的PCM数据持续编码为MP3并写入文件。

完整代码实现

import 'dart:typed_data';
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:mic_stream/mic_stream.dart';

Stream<Uint8List>? stream;
StreamSubscription<Uint8List>? listener;
// FFmpeg进程实例,用于持续写入数据
Session? ffmpegSession;

Future<void> startRecordingToMp3(String outputPath) async {
  // 1. 初始化麦克风流
  stream = await MicStream.microphone(
    audioSource: AudioSource.DEFAULT,
    sampleRate: 44100,
    channelConfig: ChannelConfig.CHANNEL_IN_STEREO,
    audioFormat: AudioFormat.ENCODING_PCM_16BIT,
  );

  // 2. 构建FFmpeg命令:指定输入为标准输入(-i -),解析PCM参数,编码为MP3
  final arguments = [
    '-f', 's16le',       // 输入格式:16位小端PCM
    '-ar', '44100',      // 采样率
    '-ac', '2',          // 声道数
    '-i', '-',           // 输入源为标准输入
    '-codec:a', 'libmp3lame', // MP3编码器
    '-b:a', '128k',      // 音频码率
    '-y',                // 覆盖已有文件
    outputPath           // 输出文件路径
  ];

  // 3. 启动FFmpeg异步进程,获取会话实例
  ffmpegSession = await FFmpegKit.executeWithArgumentsAsync(arguments, (session) async {
    final returnCode = await session.getReturnCode();
    if (ReturnCode.isSuccess(returnCode)) {
      print('MP3录制完成');
    } else if (ReturnCode.isCancel(returnCode)) {
      print('MP3录制被取消');
    } else {
      print('MP3录制失败,日志:${await session.getAllLogsAsString()}');
    }
  });

  // 4. 监听麦克风数据流,持续写入FFmpeg进程的标准输入
  listener = stream!.listen((pcmData) async {
    if (ffmpegSession != null && !await ffmpegSession!.isClosed()) {
      // 将PCM数据写入FFmpeg的标准输入
      await FFmpegKit.writeToStdin(ffmpegSession!, pcmData);
    }
  });
}

// 停止录制的方法
Future<void> stopRecording() async {
  await listener?.cancel();
  if (ffmpegSession != null) {
    // 发送EOF信号,让FFmpeg完成文件写入
    await FFmpegKit.writeToStdin(ffmpegSession!, Uint8List(0), true);
    await ffmpegSession!.wait();
    ffmpegSession = null;
  }
}

场景2:实时推流到RTSP服务器

同样需要启动持续的FFmpeg进程,将PCM数据编码后推送到RTSP服务器,推荐使用TCP传输保证稳定性。

完整代码实现

import 'dart:typed_data';
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:mic_stream/mic_stream.dart';

Stream<Uint8List>? stream;
StreamSubscription<Uint8List>? listener;
Session? ffmpegSession;

Future<void> startRtspPush(String rtspUrl) async {
  // 1. 初始化麦克风流
  stream = await MicStream.microphone(
    audioSource: AudioSource.DEFAULT,
    sampleRate: 44100,
    channelConfig: ChannelConfig.CHANNEL_IN_STEREO,
    audioFormat: AudioFormat.ENCODING_PCM_16BIT,
  );

  // 2. 构建FFmpeg推流命令
  final arguments = [
    '-f', 's16le',       // 输入格式:16位小端PCM
    '-ar', '44100',      // 采样率
    '-ac', '2',          // 声道数
    '-i', '-',           // 输入源为标准输入
    '-codec:a', 'aac',   // 推流推荐用AAC编码器(RTSP常用)
    '-b:a', '128k',      // 音频码率
    '-f', 'rtsp',        // 输出格式为RTSP
    '-rtsp_transport', 'tcp', // 使用TCP传输,避免丢包
    rtspUrl              // RTSP服务器地址
  ];

  // 3. 启动FFmpeg异步推流进程
  ffmpegSession = await FFmpegKit.executeWithArgumentsAsync(arguments, (session) async {
    final returnCode = await session.getReturnCode();
    if (ReturnCode.isSuccess(returnCode)) {
      print('RTSP推流结束');
    } else if (ReturnCode.isCancel(returnCode)) {
      print('RTSP推流被取消');
    } else {
      print('RTSP推流失败,日志:${await session.getAllLogsAsString()}');
    }
  });

  // 4. 持续写入PCM数据到FFmpeg进程
  listener = stream!.listen((pcmData) async {
    if (ffmpegSession != null && !await ffmpegSession!.isClosed()) {
      await FFmpegKit.writeToStdin(ffmpegSession!, pcmData);
    }
  });
}

// 停止推流的方法
Future<void> stopRtspPush() async {
  await listener?.cancel();
  if (ffmpegSession != null) {
    await FFmpegKit.writeToStdin(ffmpegSession!, Uint8List(0), true);
    await ffmpegSession!.wait();
    ffmpegSession = null;
  }
}

关键注意事项

  • 避免频繁启动FFmpeg进程:每次启动FFmpeg都会有初始化开销,实时流必须用单个持续进程处理所有音频数据。
  • 指定正确的PCM参数:必须严格匹配mic_stream的输出参数(采样率、声道数、位深),否则会出现音频杂音、速度异常等问题。
  • 权限申请:确保已申请录音权限和存储权限(本地保存时),Android端需要在AndroidManifest.xml中添加对应权限,iOS端在Info.plist中添加麦克风权限描述。
  • 错误排查:如果出现失败,调用session.getAllLogsAsString()获取完整日志,根据FFmpeg的错误提示调整参数或排查环境问题(比如FFmpeg是否包含所需编码器,如libmp3lame、aac)。

内容的提问来源于stack exchange,提问作者Uday

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:34:52