Canardoux / flutter_sound

Flutter plugin for sound. Audio recorder and player.
Mozilla Public License 2.0
877 stars 573 forks source link

startRecorder fails when using AndroidAudioSource.VOICE_DOWNLINK #245

Closed JayThadeshwar closed 4 years ago

JayThadeshwar commented 4 years ago

Version of flutter_sound

flutter_sound: ^2.1.1

flutter doctor

Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, v1.12.13+hotfix.5, on Linux, locale en_IN)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3) [!] Android Studio (version 3.5) ✗ Flutter plugin not installed; this adds Flutter specific functionality. ✗ Dart plugin not installed; this adds Dart specific functionality. [✓] VS Code (version 1.41.1) [✓] Connected device (1 available)

Platforms you faced the error (IOS or Android or both?)

Error faced in Android

Expected behavior

When tried to start recording using flutterSound.startRecorder with _AndroidAudioSource.VOICEDOWNLINK expected to start recording and generate mp3 file with recorded sound.

Actual behavior

Didn't started recording and generated two corrupted file.

Tested environment (Emulator? Real Device?)

Redmi Note 4 [Android 7.0]

Steps to reproduce the behavior

Calling startRecorder

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_sound/android_encoder.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:permission/permission.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'dart:math';
import 'package:phone_state_i/phone_state_i.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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 _isRecording = false;
  String data="Recording : false";
  List <String> _path = [null, null, null, null, null, null, null];
  StreamSubscription _dbPeakSubscription;
  StreamSubscription _recorderSubscription;
  StreamSubscription _callStreamSubscription;
  FlutterSound flutterSound;
  t_CODEC _codec = t_CODEC.CODEC_AAC;

  @override
  void initState() {
    super.initState();
    getPermissions();
    _callStreamSubscription = phoneStateCallEvent.listen((PhoneStateCallEvent event) {
      print('Call is Incoming or Connected: ' + event.stateC);  
      if(event.stateC=="true"){        
        onStartRecorderPressed(true);
      }
      else if(event.stateC=="false"){        
        onStartRecorderPressed(false);        
      }
    });
    flutterSound = new FlutterSound();
    flutterSound.setSubscriptionDuration(0.01);
    flutterSound.setDbPeakLevelUpdate(0.8);
    flutterSound.setDbLevelEnabled(true);
    initializeDateFormatting();
  }

  bool dirExists(Directory d){    
    d.exists().then((val){
      return val;
    });
    return false;
  }

  @override
  void dispose() {
    super.dispose();
    _callStreamSubscription.cancel();
  }

  void startRecorder() async{
    try {
      Directory tempDir = await getExternalStorageDirectory();      
      var rng = new Random();  
      int val=rng.nextInt(50000);         
      String path = await flutterSound.startRecorder(
        uri: "${tempDir.path}/${val}sound.mp3", 
        codec: _codec,
        androidAudioSource: AndroidAudioSource.VOICE_DOWNLINK,
      );
      print('startRecorder: $path');

      _dbPeakSubscription =
          flutterSound.onRecorderDbPeakChanged.listen((value) {
            print("got update -> $value");
          });
      this._isRecording = true;
      this.setState(() {        
        this._path[_codec.index] = path;
        this.data="Recording : $_isRecording";
      });
    } catch (err) {
      print ('startRecorder error: $err');
      this._isRecording = false;
      setState (()
      {                  
        this.data="Recording : $_isRecording";
      });
    }
  }

  void stopRecorder() async {
    try {
      String result = await flutterSound.stopRecorder();
      print ('stopRecorder: $result');

      if ( _recorderSubscription != null ) {
        _recorderSubscription.cancel ();
        _recorderSubscription = null;
      }
      if ( _dbPeakSubscription != null ) {
        _dbPeakSubscription.cancel ();
        _dbPeakSubscription = null;
      }
    } catch ( err ) {
      print ('stopRecorder error: $err');
    }
    this._isRecording = false;
    this.setState(() {
      this.data="Recording : $_isRecording";
    });

  }

  onStartRecorderPressed(bool callState) {
    if(callState){
      if(flutterSound.audioState == t_AUDIO_STATE.IS_STOPPED){
         print("Start recorder");
        return startRecorder();   
      }      
      else
      {
        print("Ideal");
      }
    }
    else{
      if (flutterSound.audioState == t_AUDIO_STATE.IS_RECORDING){  
        print("Stop recorder");
        return stopRecorder();
      }
    }    
  }

  @override
  Widget build(BuildContext context) {    

    return Material(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children:[ Text(data),
          ],
        ),
      ),
    );
  }

  Future<void> getPermissions() async {
    final permissions =
        await Permission.getPermissionsStatus([PermissionName.Storage,PermissionName.Microphone]);
    var request = true;
    switch (permissions[0].permissionStatus) {
      case PermissionStatus.allow:
        request = false;
        break;
      case PermissionStatus.always:
        request = false;
        break;
      default:
    }
    switch (permissions[1].permissionStatus) {
      case PermissionStatus.allow:
        request = false;
        break;
      case PermissionStatus.always:
        request = false;
        break;
      default:
    }
    if (request) {
      await Permission.requestPermissions([PermissionName.Storage,PermissionName.Microphone]);
    }
  }
}
robyroad commented 4 years ago

I think you're using the wrong codec. You're saving a file with .mp3 extension but the codec is AAC: t_CODEC _codec = t_CODEC.CODEC_AAC;

Probably you must use t_CODEC _codec = t_CODEC.CODEC_MP3;

Hope it helps

hyochan commented 4 years ago

Looks @robyroad is correct~! Closing for now.

JayThadeshwar commented 4 years ago

@robyroad @hyochan

I tried changing the codec to t_CODEC _codec = t_CODEC.CODEC_MP3 but yet getting the error. So, I tried printing the stacktrace and got:

I/flutter (31781):  FlutterSound.startRecorder (package:flutter_sound/ flutter_sound.dart:247:7)
I/flutter (31781): <asynchronous suspension>
I/flutter (31781): #1      _MyHomePageState.startRecorder (package:call_record/main.dart:88:40)
I/flutter (31781): <asynchronous suspension>
I/flutter (31781): #2      _MyHomePageState.onStartRecorderPressed (package:call_record/main.dart:142:16)
I/flutter (31781): #3      _MyHomePageState.initState.<anonymous closure> (package:call_record/main.dart:55:9)
I/flutter (31781): #4      _rootRunUnary (dart:async/zone.dart:1134:38)
I/flutter (31781): #5      _CustomZone.runUnary (dart:async/zone.dart:1031:19)
I/flutter (31781): #6      _CustomZone.runUnaryGuarded (dart:async/zone.dart:933:7)
I/flutter (31781): #7      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:338:11)
I/flutter (31781): #8      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:265:7)
I/flutter (31781): #9      _ForwardingStreamSubscription._add (dart:async/stream_pipe.dart:134:11)
I/flutter (31781): #10     _MapStream._handleData (dart:async/stream_pipe.dart:234:10)
I/flutter (31781): #11     _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:166:13)
I/flutter (31781): #12     _rootRunUnary (dart:async/zone.dart:1

On line 247 of _fluttersound.dart it throws RecorderRunningException with message Codec not supported. It seems like CODEC_MP3 is not supported. Guide me if I am wrong. As, I checked FlutterSoundPlugin.java where _isAndroidEncoderSupported boolean value returns false for the MP3.

hyochan commented 4 years ago

Can you try 3.0.0 also? If I remember correctly mp3 had license issue. @Larpoux

Larpoux commented 4 years ago

I confirm that neither Android nor iOS may record MP3 because of license issue. (but it is ok for MP3 playback)

One of my(many) plans is to support MP3 recording using lame (via FFmpeg). The problem is that actually flutter_sound cannot stream the recording to a specific codec.

Either we will have to encode to MP3 after the end of the recording, but it can delay very much the stopRecorder() for very large records. Or we must support low level recording tools on Android or iOS. This would be very good, because it will also solve #210. But probably much work to do that ...

Larpoux commented 4 years ago

Note : encoding with a specific Codec after stopRecorder() is actually what I do to record OGG/OPUS on iOS. This is fine for my own use , because my records are very short. But it would be great if it was possible to encode OGG/OPUS or MP3 during the recording.

JayThadeshwar commented 4 years ago

@hyochan @Larpoux @robyroad So, is there any way I can record call in any other format using flutter_sound plugin?? Please, elaborate on how can I achieve VOICE_DOWNLINK. Thank you for all the responses. :100:

Larpoux commented 4 years ago

Actually, on Android, your choice is very simple 🙄 : the only codec available is AAC. This will be improved very soon, I hope. So actually, if you really want another codec, you must decode and re-encode to the new format. But you cannot do that during recording. You can only do that after the end of the recording.

To record from VOICE_DOWNLINK just specify androidAudioSource: AndroidAudioSource.VOICE_DOWNLINK as a startRecorder() parameter. But I do not know if anybody has already tried this option. Perhaps you will be first !

JayThadeshwar commented 4 years ago

@Larpoux I tried using androidAudioSource: AndroidAudioSource.VOICE_DOWNLINK in startRecorder() but again I got two AAC files which are corrupted(it doesn't contain any data). Also no error or exception was raised, can you explain me what is happening?

Then inorder to check if my code is working properly or not I tried changing androidAudioSource: AndroidAudioSource.VOICE_COMMUNICATION and it works properly.

So, I think so there is some problem while recording call with VOICE_DOWNLINK.

Larpoux commented 4 years ago

@JayThadeshwar ,

This is interesting information. Probably Flutter Sound is actually not compatible with VOICE_DOWNLINK. I am tagging your issue as Enhancement requested.

github-actions[bot] commented 4 years ago

This issue is stale because it has been open 90 days with no activity. Leave a comment or this will be closed in 7 days.