Flutter webrtc音频无法在android上运行
Posted
技术标签:
【中文标题】Flutter webrtc音频无法在android上运行【英文标题】:Flutter webrtc audio not working on android 【发布时间】:2021-02-18 17:39:33 【问题描述】:在 Flutter 中,我希望在两个节点之间进行语音通话。我正在使用Flutter-WebRTC。我正在做一些测试,视频似乎正在使用 webrtc,但没有音频。 我看到远程对等方的视频,但在任何一侧都听不到任何音频。
一个是我的安卓手机,另一个是模拟器
我的 main.dart 代码是:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:sdp_transform/sdp_transform.dart';
import 'dart:developer' as developer;
void main()
runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'WebRTC lets learn together'),
);
class MyHomePage extends StatefulWidget
MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
bool _offer = false;
RTCPeerConnection _peerConnection;
MediaStream _localStream;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
final sdpController = TextEditingController();
@override
dispose()
_localRenderer.dispose();
_remoteRenderer.dispose();
sdpController.dispose();
super.dispose();
@override
void initState()
initRenderers();
_createPeerConnection().then((pc)
_peerConnection = pc;
);
super.initState();
initRenderers() async
await _localRenderer.initialize();
await _remoteRenderer.initialize();
void _createOffer() async
RTCSessionDescription description =
await _peerConnection.createOffer('offerToReceiveAudio': 1, 'offerToReceiveVideo': 1);
var session = parse(description.sdp);
print(json.encode(session));
_offer = true;
_peerConnection.setLocalDescription(description);
void _createAnswer() async
RTCSessionDescription description =
await _peerConnection.createAnswer('offerToReceiveAudio': 1, 'offerToReceiveVideo': 1);
var session = parse(description.sdp);
print(json.encode(session));
_peerConnection.setLocalDescription(description);
void _setRemoteDescription() async
String jsonString = sdpController.text;
dynamic session = await jsonDecode('$jsonString');
String sdp = write(session, null);
// RTCSessionDescription description =
// new RTCSessionDescription(session['sdp'], session['type']);
RTCSessionDescription description =
new RTCSessionDescription(sdp, _offer ? 'answer' : 'offer');
print(description.toMap());
await _peerConnection.setRemoteDescription(description);
void _addCandidate() async
String jsonString = sdpController.text;
dynamic session = await jsonDecode('$jsonString');
print(session['candidate']);
dynamic candidate =
new RTCIceCandidate(session['candidate'], session['sdpMid'], session['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
_createPeerConnection() async
Map<String, dynamic> configuration =
"iceServers": [
"url": "stun:stun.l.google.com:19302",
]
;
final Map<String, dynamic> offerSdpConstraints =
"mandatory":
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": true,
,
"optional": [],
;
_localStream = await _getUserMedia();
RTCPeerConnection pc = await createPeerConnection(configuration, offerSdpConstraints);
pc.addStream(_localStream);
pc.onIceCandidate = (e)
if (e.candidate != null)
print(json.encode(
'candidate': e.candidate.toString(),
'sdpMid': e.sdpMid.toString(),
'sdpMlineIndex': e.sdpMlineIndex,
));
;
pc.onIceConnectionState = (e)
print(e);
;
pc.onAddStream = (stream)
print('addStream: ' + stream.id);
_remoteRenderer.srcObject = stream;
;
return pc;
_getUserMedia() async
final Map<String, dynamic> mediaConstraints =
'audio': false,
'video':
'facingMode': 'user',
,
;
MediaStream stream = await MediaDevices.getUserMedia(mediaConstraints);
_localRenderer.srcObject = stream;
return stream;
SizedBox videoRenderers() => SizedBox(
height: 210,
child: Row(children: [
Flexible(
child: new Container(
key: new Key("local"),
margin: new EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
decoration: new BoxDecoration(color: Colors.black),
child: new RTCVideoView(_localRenderer)
),
),
Flexible(
child: new Container(
key: new Key("remote"),
margin: new EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 5.0),
decoration: new BoxDecoration(color: Colors.black),
child: new RTCVideoView(_remoteRenderer)),
)
]));
Row offerAndAnswerButtons() =>
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
new RaisedButton(
onPressed: _createOffer,
child: Text('Offer'),
color: Colors.amber,
),
RaisedButton(
onPressed: _createAnswer,
child: Text('Answer'),
color: Colors.amber,
),
]);
Row sdpCandidateButtons() =>
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
RaisedButton(
onPressed: _setRemoteDescription,
child: Text('Set Remote Desc'),
color: Colors.amber,
),
RaisedButton(
onPressed: _addCandidate,
child: Text('Add Candidate'),
color: Colors.amber,
)
]);
Padding sdpCandidatesTF() => Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: sdpController,
keyboardType: TextInputType.multiline,
maxLines: 4,
maxLength: TextField.noMaxLength,
),
);
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
child: Column(children: [
videoRenderers(),
offerAndAnswerButtons(),
sdpCandidatesTF(),
sdpCandidateButtons(),
])));
在 build.gradle 中,将 minSdkVersion 更改为 21。
在androidManifest.xml中,添加:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
我看到远程对等方的视频,但在任何一侧都听不到任何音频。我错过了什么吗?
【问题讨论】:
【参考方案1】:一个月前我遇到了完全相同的问题。 确保通过设置模拟器的麦克风处于活动状态并使用主机麦克风。 我需要注意的另一点是音频仅在从模拟器启动呼叫时才起作用。
当我在我的真实手机上点击通话按钮时,相机打开但音频没有。但是当我先点击模拟器上的按钮时,一切正常。
如果您使用的是 Android Studio,请注意每次启动模拟器时都会禁用使用主机音频输入的选项。
正如documentation 所说:
如果您想使用主机音频数据,您可以通过转到扩展控制 > 麦克风并启用虚拟麦克风使用主机音频输入来启用该选项。每当模拟器重新启动时,此选项会自动禁用。
【讨论】:
谢谢。您能否也请分享您使用的代码? 你也能从模拟器中获得声音吗? 对不起,我的回答迟了,很遗憾我不能把公共代码放在这里,因为我知道这是一个为客户制作的私人应用程序,但是我会将link 加入到包含以下内容的 mega.nz 文件夹中我的应用程序中与 WebRTC 相关的文件。该应用程序不需要视频,因此您只能找到麦克风的代码。要回答您的第二个问题,是的,我能够从模拟器中获得声音。我还在 Android Studio 中加入了“主机音频输入设置”的图像。希望对您有所帮助。 谢谢!因此,您正在向 peerconnection 添加音轨以在此处从手机发送音频:_localStream.getTracks().forEach((track) async => await pc.addTrack(track, _localStream));
但是请您告诉我您如何播放在 webrtc 上收到的音频?是在onTrack
事件中吗?
我的荣幸。是的,我使用 RTCVideoRenderer 播放音频,这不是最佳选择,但我没有太多时间,所以我举了这个例子(之前的 Github 链接)并删除了显示视频的小部件。正如我所说,这远非最佳,如果您有时间,我建议您更深入地了解 RTCVideoRenderer 如何播放音频并创建自己的 RTCAudioRenderer 类【参考方案2】:
_getUserMedia() async
final Map<String, dynamic> mediaConstraints =
'audio': false, // ---- Make it true
'video':
'facingMode': 'user',
,
;
使下面的音频为真。
【讨论】:
以上是关于Flutter webrtc音频无法在android上运行的主要内容,如果未能解决你的问题,请参考以下文章
如何修复在flutter webview中无法访问视频流(NotAllowedError)以使用html5 + webRTC相机api?
如何在服务器上使用 ffmpeg 从 WebRTC 流中获取音频和视频