เสียง Flutter webrtc ไม่ทำงานบน Android

ใน Flutter ฉันต้องการโทรด้วยเสียงระหว่างเพื่อนสองคน ฉันใช้ Flutter-WebRTC ฉันกำลังทำการทดสอบและดูเหมือนว่าวิดีโอจะใช้งานได้กับ webrtc แต่ไม่มีเสียง ฉันเห็นวิดีโอของเพื่อนระยะไกล แต่ไม่ได้ยินเสียงใดๆ จากด้านใดด้านหนึ่ง

เพื่อนคนหนึ่งคือโทรศัพท์ Android ของฉัน และอีกคนหนึ่งคือโปรแกรมจำลอง

รหัส 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" />

ฉันเห็นวิดีโอของเพื่อนระยะไกล แต่ไม่ได้ยินเสียงใดๆ จากด้านใดด้านหนึ่ง ฉันพลาดอะไรไปรึเปล่า?


person user5155835    schedule 18.02.2021    source แหล่งที่มา


คำตอบ (1)


ฉันประสบปัญหาเดียวกันทุกประการเมื่อเดือนที่แล้ว ตรวจสอบให้แน่ใจว่าได้ไปที่การตั้งค่าของคุณว่าไมโครโฟนของโปรแกรมจำลองทำงานอยู่และใช้ไมโครโฟนของโฮสต์ อีกประเด็นที่ฉันต้องดูแลคือเสียงจะทำงานเมื่อมีการโทรจากโปรแกรมจำลองเท่านั้น

เมื่อฉันคลิกปุ่มโทรบนโทรศัพท์จริง กล้องเปิดขึ้นแต่ไม่มีเสียง แต่เมื่อฉันคลิกปุ่มบนโปรแกรมจำลองก่อน ทุกอย่างทำงานได้ดี

หากคุณใช้ Android studio ระวังตัวเลือกในการใช้อินพุตเสียงของโฮสต์จะถูกปิดใช้งานทุกครั้งที่คุณเปิดตัวจำลอง

ตามที่เอกสารประกอบบอกว่า:

หากคุณต้องการใช้ข้อมูลเสียงของโฮสต์ คุณสามารถเปิดใช้งานตัวเลือกนั้นได้โดยไปที่การควบคุมเพิ่มเติม > ไมโครโฟน และการเปิดใช้งานไมโครโฟนเสมือนจะใช้อินพุตเสียงของโฮสต์ ตัวเลือกนี้จะถูกปิดใช้งานโดยอัตโนมัติทุกครั้งที่รีสตาร์ทโปรแกรมจำลอง

person Tanguy Lhinares    schedule 19.02.2021
comment
ขอบคุณ คุณช่วยแบ่งปันรหัสที่คุณใช้ได้ไหม - person user5155835; 19.02.2021
comment
คุณสามารถรับเสียงจากโปรแกรมจำลองได้หรือไม่? - person user5155835; 19.02.2021
comment
ขออภัยที่ตอบช้า ฉันไม่สามารถใส่รหัสสาธารณะที่นี่โดยรู้ว่านี่เป็นแอปพลิเคชันส่วนตัวที่สร้างขึ้นสำหรับลูกค้า แต่ฉันจะเข้าร่วม ลิงก์ไปยังโฟลเดอร์ mega.nz ที่มีไฟล์ที่เกี่ยวข้องกับ WebRTC ในแอปของฉัน แอปพลิเคชันไม่ต้องการวิดีโอ ดังนั้นคุณจะพบเพียงโค้ดสำหรับไมโครโฟนเท่านั้น เพื่อตอบคำถามที่สองของคุณ ใช่ ฉันสามารถรับเสียงจากโปรแกรมจำลองได้ ฉันยังได้รวมรูปภาพสำหรับการตั้งค่าอินพุตเสียงของโฮสต์ใน Android Studio ด้วย หวังว่ามันจะช่วยได้ในทางใดทางหนึ่ง - person Tanguy Lhinares; 19.02.2021
comment
ขอบคุณ! ดังนั้น คุณกำลังเพิ่มแทร็กในการเชื่อมต่อแบบเพียร์เพื่อส่งเสียงจากโทรศัพท์ที่นี่: _localStream.getTracks().forEach((track) async => await pc.addTrack(track, _localStream)); แต่โปรดแจ้งให้เราทราบว่าคุณเล่นเสียงที่ได้รับจาก webrtc ได้อย่างไร มันอยู่ในเหตุการณ์ onTrack หรือไม่? - person user5155835; 19.02.2021
comment
ฉันไม่รู้เชิงลึกเกี่ยวกับไลบรารีของ webrtc และไม่มีเวลาเจาะลึกเกี่ยวกับโปรเจ็กต์นี้ แต่สามารถแนะนำลิงก์ทั้งสองที่นำทางให้ฉันพัฒนาทุกอย่างได้ Github - Flutter Webrtc และ webrtc เอกสารอย่างเป็นทางการ - person Tanguy Lhinares; 19.02.2021
comment
ขอขอบคุณอีกครั้ง. โปรดแจ้งให้เราทราบว่าคุณใช้ RTCVideoRenderer ในการเล่นเสียงหรือไม่ หรือเป็นอย่างอื่น? - person user5155835; 27.02.2021
comment
ด้วยความยินดี. ใช่ ฉันใช้ RTCVideoRenderer เพื่อเล่นเสียง ซึ่งไม่เหมาะสม แต่ฉันไม่มีเวลามากนัก ดังนั้นฉันจึงเอาตัวอย่าง (ลิงก์ Github ก่อนหน้า) และลบวิดเจ็ตที่กำลังแสดงวิดีโอ อย่างที่บอกไปว่านี่ยังห่างไกลจากการเหมาะสมที่สุด และหากคุณมีเวลา ฉันขอแนะนำให้คุณดูเชิงลึกมากขึ้นว่า RTCVideoRenderer เล่นเสียงอย่างไร และสร้างคลาส RTCAudioRenderer ของคุณเอง - person Tanguy Lhinares; 01.03.2021