You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
7.7 KiB

import 'dart:typed_data';
import 'package:flutter/material.dart';
import '../../lib/opus.dart';
void main() {
runApp(const OpusExampleApp());
}
class OpusExampleApp extends StatelessWidget {
const OpusExampleApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Opus Plugin Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const OpusExamplePage(),
);
}
}
class OpusExamplePage extends StatefulWidget {
const OpusExamplePage({Key? key}) : super(key: key);
@override
_OpusExamplePageState createState() => _OpusExamplePageState();
}
class _OpusExamplePageState extends State<OpusExamplePage> {
// 状态变量
bool _encoderInitialized = false;
bool _decoderInitialized = false;
String _statusMessage = '等待初始化...';
// 模拟PCM数据
Uint8List? _samplePcmData;
Uint8List? _encodedData;
Uint8List? _decodedData;
@override
void initState() {
super.initState();
_initOpus();
}
@override
void dispose() {
_disposeOpus();
super.dispose();
}
// 初始化Opus编解码器
Future<void> _initOpus() async {
setState(() {
_statusMessage = '正在初始化编解码器...';
});
// 初始化编码器
final encoderResult = await Opus.createEncoder(
sampleRate: 48000,
channels: 2,
application: OpusApplicationType.audio,
complexity: 10,
frameSize: 960,
);
// 初始化解码器
final decoderResult = await Opus.createDecoder(
sampleRate: 48000,
channels: 2,
);
setState(() {
_encoderInitialized = encoderResult;
_decoderInitialized = decoderResult;
_statusMessage = _encoderInitialized && _decoderInitialized
? '编解码器初始化成功'
: '编解码器初始化失败';
});
// 创建模拟PCM数据用于测试
_createSamplePcmData();
}
// 创建模拟PCM数据(正弦波)
void _createSamplePcmData() {
// 创建一个简单的正弦波
const int sampleRate = 48000;
const int channels = 2;
const int frequency = 440; // 440Hz - A4音符
const int durationMs = 20; // 20ms,按照常规frameSize=960
const int frameSize = 960; // 对应20ms@48kHz
// 创建PCM数据缓冲区,16位有符号整数,每采样2字节,双声道
final int samplesToGenerate = (sampleRate * durationMs / 1000).round();
final int bufferSize = samplesToGenerate * channels * 2; // 2字节/样本
final byteBuffer = ByteData(bufferSize);
// 生成正弦波
for (int i = 0; i < samplesToGenerate; i++) {
// 计算当前时间点
final double t = i / sampleRate;
// 计算振幅值 (-1.0 to 1.0)
final double amplitude = sin(2 * 3.14159265359 * frequency * t);
// 缩放到16位有符号整数范围 (-32768 to 32767)
final int sample = (amplitude * 32767).round();
// 为所有声道写入相同的值
for (int channel = 0; channel < channels; channel++) {
final int offset = (i * channels + channel) * 2;
byteBuffer.setInt16(offset, sample, Endian.little);
}
}
_samplePcmData = Uint8List.fromList(
List.generate(bufferSize, (index) => byteBuffer.getUint8(index)));
setState(() {
_statusMessage = '已创建${_samplePcmData!.length}字节测试PCM数据';
});
}
// 编码PCM数据
Future<void> _encodeData() async {
if (!_encoderInitialized || _samplePcmData == null) {
setState(() {
_statusMessage = '编码器未初始化或没有PCM数据';
});
return;
}
setState(() {
_statusMessage = '正在编码...';
});
final encodedData = await Opus.encodeData(_samplePcmData!);
setState(() {
_encodedData = encodedData;
_statusMessage = encodedData != null
? '编码成功:${_samplePcmData!.length}字节PCM -> ${encodedData.length}字节Opus'
: '编码失败';
});
}
// 解码Opus数据
Future<void> _decodeData() async {
if (!_decoderInitialized || _encodedData == null) {
setState(() {
_statusMessage = '解码器未初始化或没有编码数据';
});
return;
}
setState(() {
_statusMessage = '正在解码...';
});
final decodedData = await Opus.decodeData(_encodedData!, frameSize: 960);
setState(() {
_decodedData = decodedData;
_statusMessage = decodedData != null
? '解码成功:${_encodedData!.length}字节Opus -> ${decodedData.length}字节PCM'
: '解码失败';
});
}
// 释放资源
Future<void> _disposeOpus() async {
if (_encoderInitialized) {
await Opus.disposeEncoder();
}
if (_decoderInitialized) {
await Opus.disposeDecoder();
}
}
// 正弦波函数
double sin(double x) {
// 简单泰勒展开实现
double result = 0;
double term = x;
for (int i = 1; i <= 7; i += 2) {
result += term;
term = -term * x * x / ((i + 1) * (i + 2));
}
return result;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Opus编解码示例'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'状态:$_statusMessage',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text('编码器:${_encoderInitialized ? "已初始化" : "未初始化"}'),
Text('解码器:${_decoderInitialized ? "已初始化" : "未初始化"}'),
],
),
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _initOpus,
child: const Text('初始化编解码器'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _encodeData,
child: const Text('编码测试数据'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _decodeData,
child: const Text('解码测试数据'),
),
const SizedBox(height: 16),
if (_samplePcmData != null)
_buildDataInfoCard('源PCM数据', _samplePcmData!),
const SizedBox(height: 8),
if (_encodedData != null)
_buildDataInfoCard('编码后的Opus数据', _encodedData!),
const SizedBox(height: 8),
if (_decodedData != null)
_buildDataInfoCard('解码后的PCM数据', _decodedData!),
],
),
),
);
}
Widget _buildDataInfoCard(String title, Uint8List data) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text('大小: ${data.length} 字节'),
const SizedBox(height: 4),
Text('前16字节: ${_formatBytes(data)}'),
],
),
),
);
}
String _formatBytes(Uint8List data) {
final length = data.length > 16 ? 16 : data.length;
return data
.sublist(0, length)
.map((e) => e.toRadixString(16).padLeft(2, '0'))
.join(' ');
}
}