FlutterのrecordのAudioEncoderのファイル出力タイプごとの特徴をまとめてみた

# recordとは

Flutterで音声ファイルを出力できるパッケージ
AudioEncoderはrecordで定められているクラス

# この記事の注意点

1m秒は1ミリ秒を表しています。

# AudioEncoderが対応しているファイルの種類

```audio_encoder.dart
/// Audio encoder to be used for recording.
enum AudioEncoder {
  /// MPEG-4 AAC Low complexity
  /// Will output to MPEG_4 format container.
  aacLc,

  /// MPEG-4 AAC Enhanced Low Delay
  /// Will output to MPEG_4 format container.
  aacEld,

  /// MPEG-4 High Efficiency AAC (Version 2 if available)
  /// Will output to MPEG_4 format container.
  aacHe,

  /// The AMR (Adaptive Multi-Rate) narrow band speech.
  /// sampling rate should be set to 8kHz.
  /// Will output to 3GP format container on Android.
  amrNb,

  /// The AMR (Adaptive Multi-Rate) wide band speech.
  /// sampling rate should be set to 16kHz.
  /// Will output to 3GP format container on Android.
  amrWb,

  /// Will output to MPEG_4 format container.
  ///
  /// SDK 29 on Android
  ///
  /// SDK 11 on iOs
  opus,

  /// Ogg Vorbis Audio
  vorbisOgg,

  /// Free Lossless Audio Codec
  flac,

  /// Waveform Audio (pcm16bit with headers)
  wav,

  /// Linear PCM 8 bit per sample
  pcm8bit,

  /// Linear PCM 16 bit per sample
  pcm16bit,
}

```

# サイズの検証方法

約5秒間、無音の音声を録音して、1m秒あたりのサイズを計算します。

# aacLc
## 1m秒あたりのサイズ
12.45 バイト
## 計算方法
65,090 / 5226
## FirebaseStorageに認識されるファイルタイプ

video/mp4

# aacEld
## 1m秒あたりのサイズ
16.35
## 計算方法
84,224 / 5151
## FirebaseStorageに認識されるファイルタイプ
video/mp4

# aacHe
## 1m秒あたりのサイズ
16.45
## 計算方法
84,966 / 5164
## FirebaseStorageに認識されるファイルタイプ
video/mp4

# amrNb
## 1m秒あたりのサイズ
2.17
## 計算方法
11,616 / 5330
## FirebaseStorageに認識されるファイルタイプ

video/3gpp
# amrWb
## 1m秒あたりのサイズ
3.51
## 計算方法
18,513 / 5272
## FirebaseStorageに認識されるファイルタイプ

video/3gpp

# opus
## 1m秒あたりのサイズ
15.9
## 計算方法
82,668 / 5205
## FirebaseStorageに認識されるファイルタイプ
application/ogg

# vorbisOgg
## 1m秒あたりのサイズ
0
## FirebaseStorageに認識されるファイルタイプ
application/octet-stream
## コメント
なぜか0バイトでした。
詳しい方、教えてください。

# flac
## コメント
後述するコードでこの形式だけuploadできませんでした。

# wav
## 1m秒あたりのサイズ
176.7
## 計算方法
935,468 / 5293
## FirebaseStorageに認識されるファイルタイプ

audio/x-wav

# pcm8bit
## 1m秒あたりのサイズ
88.67
## 計算方法
467,712 / 5275
## FirebaseStorageに認識されるファイルタイプ
application/octet-stream

# pcm16bit
## 1m秒あたりのサイズ
177.0
## 計算方法
935,424 / 5283
## FirebaseStorageに認識されるファイルタイプ
application/octet-stream

# 小さい順のランキング
名称	1m秒あたりのサイズ (byte)	FirebaseStorageに認識されるファイルタイプ
flac	-	-
vorbisOgg	0	application/octet-stream
amrNb	2.17	video/3gpp
amrWb	3.51	video/3gpp
aacLc	12.45	video/mp4
opus	15.9	application/ogg
aacEld	16.35	video/mp4
aacHe	16.45	video/mp4
pcm8bit	88.67	application/octet-stream
wav	176.7	audio/x-wav
pcm16bit	177.0	application/octet-stream


# 検証したソースコード

```main.dart
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';

import 'dart:async';
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:record/record.dart';
import 'package:record_app/gen/firebase_options.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp() : super();
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: RecordPage(),);
  }
}

class RecordPage extends StatefulWidget {
  @override
  _RecordPageState createState() => _RecordPageState();
}

class _RecordPageState extends State<RecordPage> {
  final _encoder = AudioEncoder.pcm16bit; // ここを変化させて、再ビルドし検証する.
  String _path = "";
  Record recorder = Record();
  final _textStyle = const TextStyle(fontSize: 20);
  late File _audioFile;
  final StopWatchTimer _stopWatchTimer = StopWatchTimer();
  void _startMeasure() => _stopWatchTimer.onStartTimer();

  void _stopMeasure() => _stopWatchTimer.onStopTimer();

  void _resetMeasure() => _stopWatchTimer.onResetTimer();

  Future<void> _startRecording() async {
    bool x = await recorder.hasPermission();
    if (x) {
      _startMeasure();
      try {
        final dir = await getApplicationDocumentsDirectory();
        _path = '${dir.path}/${DateTime.now().millisecondsSinceEpoch}.m4a';
        _audioFile = File(_path);
        await recorder.start(path: _path,encoder: _encoder);
      } catch (e) {
        print(e);
      }
    }
  }
  Future<void> _stopRecording() async {
    _stopMeasure();
    await recorder.stop();
  }
  void _resetRecording() {
    recorder = Record();
    _resetMeasure();
  }

  Future<void> _upload(int value) async {
  _resetMeasure();
  if (_audioFile.existsSync()) {
    final storageRef = FirebaseStorage.instance.ref().child("audio").child(_encoder.name).child("$value-millisec");
    try {
      final uploadTask = storageRef.putFile(_audioFile);
      await uploadTask.whenComplete(() => debugPrint("アップロードが完了しました"));
    } catch(e) {
      debugPrint(e.toString());
    }
  } else {
    debugPrint("_audioFileがnullまたは存在しません");
  }
}
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Record Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
              iconSize: 50,
              icon: const Icon(Icons.mic),
              onPressed: () => _startRecording(),
            ),
            IconButton(
              iconSize: 50,
              icon: const Icon(Icons.stop),
              onPressed: () => _stopRecording(),
            ),
            IconButton(
              iconSize: 50,
              icon: const Icon(Icons.restart_alt),
              onPressed: _resetRecording,
            ),
            const SizedBox(height: 16),
            Center(
              child: StreamBuilder<int?>(
                stream: _stopWatchTimer.rawTime,
                initialData: _stopWatchTimer.rawTime.value,
                builder: (_,snapshot) {
                  final value = snapshot.data!.toInt();
                  return Text(StopWatchTimer.getDisplayTime(value,milliSecond: false,hours: false,minute: false),style: _textStyle,);
                },
              ),
            ),
            IconButton(
              iconSize: 50,
              icon: const Icon(Icons.upload),
              onPressed: () async => await _upload(_stopWatchTimer.rawTime.value)
            ),
          ],
        ),
      )
    );
  }
}

```