Audio webrtc Flutter tidak berfungsi di Android

Di Flutter, saya ingin melakukan panggilan suara antara dua rekan. Saya menggunakan Flutter-WebRTC. Saya sedang melakukan beberapa pengujian dan video sepertinya berfungsi dengan webrtc, tetapi tidak ada audio. Saya melihat video dari rekan jarak jauh, tetapi tidak mendengar audio apa pun di sisi mana pun.

Satu rekan adalah ponsel Android saya, dan lainnya adalah emulator

Kode main.dart saya adalah:

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(),
        ])));
  }
}

Di build.gradle, ubah minSdkVersion menjadi 21.

Di AndroidManifest.xml, ditambahkan:

<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" />

Saya melihat video dari rekan jarak jauh, tetapi tidak mendengar audio apa pun di sisi mana pun. Apakah saya melewatkan sesuatu?


person user5155835    schedule 18.02.2021    source sumber


Jawaban (1)


Saya menghadapi masalah yang sama persis sebulan yang lalu. Pastikan dengan masuk ke pengaturan Anda bahwa mikrofon emulator aktif dan menggunakan mikrofon host. Hal lain yang perlu saya perhatikan adalah audio hanya berfungsi saat panggilan dimulai dari emulator.

Ketika saya mengklik tombol panggil di ponsel asli saya, kamera menyala tetapi audionya tidak menyala. Namun saat saya klik tombol emulatornya terlebih dahulu, semuanya berfungsi dengan baik.

Jika Anda menggunakan Android studio, berhati-hatilah karena opsi untuk menggunakan input audio host dinonaktifkan setiap kali Anda meluncurkan emulator.

Seperti yang dikatakan dalam dokumentasi :

Jika Anda ingin menggunakan data audio host, Anda dapat mengaktifkan opsi tersebut dengan membuka Kontrol Tambahan › Mikrofon dan mengaktifkan Mikrofon virtual menggunakan input audio host. Opsi ini otomatis dinonaktifkan setiap kali emulator dimulai ulang.

person Tanguy Lhinares    schedule 19.02.2021
comment
Terima kasih. Bisakah Anda juga membagikan kode yang telah Anda gunakan? - person user5155835; 19.02.2021
comment
Apakah Anda juga bisa mendapatkan suara dari emulator? - person user5155835; 19.02.2021
comment
Maaf atas jawaban saya yang terlambat, sayangnya saya tidak dapat memasukkan kode publik di sini karena mengetahui bahwa ini adalah aplikasi pribadi yang dibuat untuk klien namun saya akan bergabung dengan tautan ke folder mega.nz yang berisi file yang terkait dengan WebRTC di aplikasi saya. Aplikasi tidak memerlukan video sehingga Anda hanya akan menemukan kode untuk mikrofon. Untuk menjawab pertanyaan kedua Anda, ya, saya bisa mendapatkan suara dari emulator. Saya juga menggabungkan gambar untuk pengaturan input audio host di Android Studio. Semoga ini bisa membantu dalam hal apa pun. - person Tanguy Lhinares; 19.02.2021
comment
Terima kasih! Jadi Anda menambahkan trek ke koneksi rekan untuk mengirim audio dari ponsel di sini: _localStream.getTracks().forEach((track) async => await pc.addTrack(track, _localStream)); Tapi bisakah Anda memberi tahu saya bagaimana Anda memutar audio yang diterima di webrtc? Apakah itu di acara onTrack? - person user5155835; 19.02.2021
comment
Saya tidak mengetahui perpustakaan webrtc secara mendalam dan tidak memiliki waktu untuk mempelajari lebih dalam proyek ini, tetapi saya dapat memberi tahu Anda dua tautan yang memandu saya untuk mengembangkan semuanya. Github - Flutter Webrtc dan webrtc dokumentasi resmi - person Tanguy Lhinares; 19.02.2021
comment
Terima kasih lagi. Bisakah Anda memberi tahu saya apakah Anda menggunakan RTCVideoRenderer sendiri untuk memutar audio? atau apakah itu sesuatu yang lain? - person user5155835; 27.02.2021
comment
Dengan senang hati. Ya saya menggunakan RTCVideoRenderer untuk memutar audio, ini tidak optimal tetapi saya tidak punya banyak waktu jadi saya mengambil contoh (link Github sebelumnya) dan menghapus widget yang menampilkan video. Seperti yang saya katakan, ini jauh dari optimal dan jika Anda punya waktu, saya menyarankan Anda untuk melihat lebih dalam tentang bagaimana RTCVideoRenderer memutar audio dan membuat kelas RTCAudioRenderer Anda sendiri - person Tanguy Lhinares; 01.03.2021