플러터:마이크 입력: 두 판 사이의 차이
보이기
잔글편집 요약 없음 |
잔글편집 요약 없음 |
||
| 51번째 줄: | 51번째 줄: | ||
== 데시벨 측정 예시 코드 == | == 데시벨 측정 예시 코드 == | ||
마찬가지로 패키지 버전에 따라 함수명이 달라지기도 함. 오래되면 문제가 발생할 수 있음.<syntaxhighlight lang="dart"> | 마찬가지로 패키지 버전에 따라 함수명이 달라지기도 함. 오래되면 문제가 발생할 수 있음. | ||
= Flutter 데시벨 측정기 학습 위키 = | |||
== 프로젝트 개요 == | |||
* '''목표''': 실시간 마이크 입력으로 데시벨 측정 | |||
* '''난이도''': 초급 (코드 67줄) | |||
* '''학습 시간''': 1-2시간 | |||
* '''플랫폼''': ✅ Android / ✅ iOS / ❌ Windows / ❌ Web | |||
== 필요한 패키지 == | |||
<syntaxhighlight lang="yaml"> | |||
# pubspec.yaml | |||
dependencies: | |||
flutter: | |||
sdk: flutter | |||
noise_meter: ^5.0.1 # 🎤 마이크 데시벨 측정 | |||
permission_handler: ^11.3.1 # 🔒 마이크 권한 관리 | |||
</syntaxhighlight> | |||
== 권한 설정 == | |||
=== Android === | |||
파일: <code>android/app/src/main/AndroidManifest.xml</code> | |||
<syntaxhighlight lang="xml"> | |||
<uses-permission android:name="android.permission.RECORD_AUDIO" /> | |||
</syntaxhighlight> | |||
=== iOS === | |||
파일: <code>ios/Runner/Info.plist</code> | |||
<syntaxhighlight lang="xml"> | |||
<key>NSMicrophoneUsageDescription</key> | |||
<string>데시벨 측정을 위해 마이크 권한이 필요합니다.</string> | |||
</syntaxhighlight> | |||
== 완전한 소스코드 == | |||
<syntaxhighlight lang="dart"> | |||
// filepath: c:\Temp\for device\decibel_app\lib\main.dart | |||
import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||
import 'package:noise_meter/noise_meter.dart'; | |||
import 'package:permission_handler/permission_handler.dart'; | import 'package:permission_handler/permission_handler.dart'; | ||
import ' | import 'dart:async'; | ||
void main() => runApp(MyApp()); | |||
class MyApp extends StatelessWidget { | |||
@override | |||
Widget build(BuildContext context) { | |||
return MaterialApp( | |||
title: '데시벨 측정기', | |||
home: DecibelMeter(), | |||
); | |||
} | |||
} | } | ||
class | class DecibelMeter extends StatefulWidget { | ||
@override | @override | ||
_DecibelMeterState createState() => _DecibelMeterState(); | |||
} | } | ||
class | class _DecibelMeterState extends State<DecibelMeter> { | ||
double _currentDB = 0.0; | |||
StreamSubscription<NoiseReading>? | bool _isListening = false; | ||
StreamSubscription<NoiseReading>? _noiseSubscription; | |||
Future<void> _startListening() async { | |||
Future<void> | |||
// 마이크 권한 요청 | // 마이크 권한 요청 | ||
var status = await Permission.microphone.request(); | |||
if (status.isGranted) { | |||
_noiseSubscription = NoiseMeter().noise.listen( | |||
(NoiseReading noiseReading) { | |||
setState(() { | |||
_currentDB = noiseReading.meanDecibel; | |||
}); | |||
}, | |||
onError: (error) { | |||
print('오류: $error'); | |||
}, | |||
); | |||
setState(() { | |||
_isListening = true; | |||
}); | }); | ||
} | } | ||
} | } | ||
void _stopListening() { | |||
void | _noiseSubscription?.cancel(); | ||
setState(() { | |||
_isListening = false; | |||
}); | |||
} | } | ||
@override | @override | ||
Widget build(BuildContext context) { | Widget build(BuildContext context) { | ||
return | return Scaffold( | ||
appBar: AppBar( | |||
title: Text('데시벨 측정기'), | |||
), | |||
body: Center( | |||
child: Column( | |||
mainAxisAlignment: MainAxisAlignment.center, | |||
children: [ | |||
Text( | |||
'${_currentDB.toInt()} dB', | |||
style: TextStyle(fontSize: 48), | |||
), | |||
SizedBox(height: 20), | |||
ElevatedButton( | |||
onPressed: _isListening ? _stopListening : _startListening, | |||
child: Text(_isListening ? '정지' : '시작'), | |||
), | |||
], | |||
), | ), | ||
), | ), | ||
); | ); | ||
} | } | ||
}</syntaxhighlight> | } | ||
</syntaxhighlight> | |||
== 코드 구조 분석 == | |||
=== 핵심 클래스들 === | |||
<syntaxhighlight lang="dart"> | |||
// 📊 데시벨 읽기 | |||
NoiseMeter().noise.listen((NoiseReading reading) { | |||
double db = reading.meanDecibel; // 평균 데시벨 | |||
}); | |||
// 🔒 권한 요청 | |||
Permission.microphone.request(); | |||
// 🎛️ 스트림 제어 | |||
StreamSubscription<NoiseReading>? subscription; | |||
</syntaxhighlight> | |||
=== 상태 관리 === | |||
<syntaxhighlight lang="dart"> | |||
class _DecibelMeterState extends State<DecibelMeter> { | |||
double _currentDB = 0.0; // 현재 데시벨 값 | |||
bool _isListening = false; // 측정 중인지 상태 | |||
StreamSubscription<NoiseReading>? _noiseSubscription; // 스트림 구독 객체 | |||
} | |||
</syntaxhighlight> | |||
== 동작 흐름 == | |||
# 앱 시작 | |||
# 마이크 권한 요청 | |||
# 권한 허용 시 NoiseMeter 시작 | |||
# 실시간 데시벨 스트림 수신 | |||
# UI 업데이트 (setState) | |||
# 정지 버튼으로 스트림 취소 | |||
== 주요 학습 포인트 == | |||
=== 1. 스트림(Stream) 패턴 === | |||
<syntaxhighlight lang="dart"> | |||
// 스트림 구독 | |||
_subscription = NoiseMeter().noise.listen( | |||
(data) => setState(() => _currentDB = data.meanDecibel), | |||
onError: (error) => print('에러: $error'), | |||
); | |||
// 메모리 누수 방지 | |||
_subscription?.cancel(); | |||
</syntaxhighlight> | |||
=== 2. 권한 관리 === | |||
<syntaxhighlight lang="dart"> | |||
Future<void> _requestPermission() async { | |||
var status = await Permission.microphone.request(); | |||
if (status.isGranted) { | |||
// 권한 허용됨 | |||
} else if (status.isDenied) { | |||
// 권한 거부됨 | |||
} else if (status.isPermanentlyDenied) { | |||
// 영구 거부됨 - 설정으로 유도 | |||
openAppSettings(); | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== 3. 생명주기 관리 === | |||
<syntaxhighlight lang="dart"> | |||
@override | |||
void dispose() { | |||
_noiseSubscription?.cancel(); // 위젯 해제 시 스트림 정리 | |||
super.dispose(); | |||
} | |||
</syntaxhighlight> | |||
== 데시벨 참고값 == | |||
{| class="wikitable" | |||
! 데시벨(dB) !! 소음 수준 !! 예시 | |||
|- | |||
| 0-20 || 매우 조용 || 도서관, 속삭임 | |||
|- | |||
| 20-40 || 조용 || 조용한 사무실 | |||
|- | |||
| 40-60 || 보통 || 일반 대화 | |||
|- | |||
| 60-80 || 시끄러움 || TV, 음악 | |||
|- | |||
| 80-100 || 매우 시끄러움 || 지하철, 공사장 | |||
|- | |||
| 100+ || 위험 || 콘서트, 제트기 | |||
|} | |||
== UI 개선 아이디어 == | |||
=== 데시벨 수준별 색상 === | |||
<syntaxhighlight lang="dart"> | |||
Color _getDecibelColor(double db) { | |||
if (db < 40) return Colors.green; // 조용 | |||
if (db < 70) return Colors.yellow; // 보통 | |||
return Colors.red; // 시끄러움 | |||
} | |||
</syntaxhighlight> | |||
=== 프로그레스 바 === | |||
<syntaxhighlight lang="dart"> | |||
LinearProgressIndicator( | |||
value: _currentDB / 120.0, // 120dB 기준 | |||
backgroundColor: Colors.grey[300], | |||
valueColor: AlwaysStoppedAnimation(_getDecibelColor(_currentDB)), | |||
) | |||
</syntaxhighlight> | |||
== 트러블슈팅 == | |||
=== 권한 문제 === | |||
<syntaxhighlight lang="dart"> | |||
// 권한 상태 확인 | |||
var status = await Permission.microphone.status; | |||
if (status.isPermanentlyDenied) { | |||
// 사용자를 설정으로 유도 | |||
showDialog(...); | |||
} | |||
</syntaxhighlight> | |||
=== 플랫폼별 이슈 === | |||
* '''Android''': <code>RECORD_AUDIO</code> 권한 필수 | |||
* '''iOS''': <code>Info.plist</code>에 사용 목적 명시 필수 | |||
* '''Windows/Web''': <code>noise_meter</code> 패키지 미지원 | |||
=== 성능 최적화 === | |||
<syntaxhighlight lang="dart"> | |||
// 백그라운드에서 정지 | |||
@override | |||
void didChangeAppLifecycleState(AppLifecycleState state) { | |||
if (state == AppLifecycleState.paused) { | |||
_stopListening(); // 배터리 절약 | |||
} | |||
} | |||
</syntaxhighlight> | |||
== 플랫폼 지원 현황 == | |||
{| class="wikitable" | |||
! 플랫폼 !! 지원 여부 !! 이유 | |||
|- | |||
| '''Android''' || ✅ || 완전 지원 | |||
|- | |||
| '''iOS''' || ✅ || 완전 지원 | |||
|- | |||
| '''Windows''' || ❌ || noise_meter 미지원 | |||
|- | |||
| '''Web''' || ❌ || 마이크 API 제한 | |||
|- | |||
| '''macOS''' || ❓ || 테스트 필요 | |||
|- | |||
| '''Linux''' || ❓ || 테스트 필요 | |||
|} | |||
== 다음 단계 학습 == | |||
=== 중급 기능 === | |||
* 📈 실시간 그래프 (charts_flutter) | |||
* 💾 측정값 저장 (shared_preferences) | |||
* 🔔 임계값 알림 (flutter_local_notifications) | |||
=== 고급 기능 === | |||
* 🎵 주파수 분석 (FFT) | |||
* 📊 통계 분석 (평균, 최대, 최소) | |||
* 🌐 데이터 공유 (Firebase) | |||
== 완성된 기능 == | |||
* ✅ 실시간 데시벨 측정 | |||
* ✅ 마이크 권한 관리 | |||
* ✅ 간단한 시작/정지 제어 | |||
* ✅ 크로스 플랫폼 (Android/iOS) | |||
* ✅ 메모리 안전 (스트림 정리) | |||
이 프로젝트로 '''Flutter의 스트림, 권한, 네이티브 패키지 사용법'''을 모두 학습할 수 있습니다! 🎉 | |||
2025년 11월 19일 (수) 05:48 판
- 플러터:개요
- 플러터:실행
- 플러터:개념 잡기
- 권한 사용
- 위젯
- 플러터:DB연결
- 플러터:Firebase(미완)
- 플러터:MySQL(미완)
- 디자인
- 플러터:배포
- 플러터:배포(안드로이드)(미완)
- 플러터:참고자료
- 플러터:위젯
- 플러터:구글 AdMob(미완)
- 플러터:라이브러리
개요
기기에서 마이크를 사용하는 법.
사전준비 권한 설정
권한 부여
| 항목 | 설명 | 비고 |
|---|---|---|
| 안드로이드 | android/app/src/main/AndroidManifest.xml 에 넣는다. |
|
| 아이폰 | ios/Runner/Info.plist에 넣는다. | <key>NSMicrophoneUsageDescription</key>
<string>앱에서 음성 입력을 사용합니다.</string> |
| Windows,
Web |
별도 설정 불필요. 자동으로 팝업이 뜸. |
필요한 패키지 점검
| 항목 | 설명 | 비고 |
|---|---|---|
| 패키지 설치 | dependencies:
flutter: sdk: flutter permission_handler: ^11.0.0 noise_meter: ^5.0.2 |
noise_meter는 db를 읽는다.
아마 시간이 지나면 오래된 버전이라 문제가 발생할 수 있음. flutter pub get |
데시벨 측정 예시 코드
마찬가지로 패키지 버전에 따라 함수명이 달라지기도 함. 오래되면 문제가 발생할 수 있음.
Flutter 데시벨 측정기 학습 위키
프로젝트 개요
- 목표: 실시간 마이크 입력으로 데시벨 측정
- 난이도: 초급 (코드 67줄)
- 학습 시간: 1-2시간
- 플랫폼: ✅ Android / ✅ iOS / ❌ Windows / ❌ Web
필요한 패키지
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
noise_meter: ^5.0.1 # 🎤 마이크 데시벨 측정
permission_handler: ^11.3.1 # 🔒 마이크 권한 관리
권한 설정
Android
파일: android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
iOS
파일: ios/Runner/Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>데시벨 측정을 위해 마이크 권한이 필요합니다.</string>
완전한 소스코드
// filepath: c:\Temp\for device\decibel_app\lib\main.dart
import 'package:flutter/material.dart';
import 'package:noise_meter/noise_meter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '데시벨 측정기',
home: DecibelMeter(),
);
}
}
class DecibelMeter extends StatefulWidget {
@override
_DecibelMeterState createState() => _DecibelMeterState();
}
class _DecibelMeterState extends State<DecibelMeter> {
double _currentDB = 0.0;
bool _isListening = false;
StreamSubscription<NoiseReading>? _noiseSubscription;
Future<void> _startListening() async {
// 마이크 권한 요청
var status = await Permission.microphone.request();
if (status.isGranted) {
_noiseSubscription = NoiseMeter().noise.listen(
(NoiseReading noiseReading) {
setState(() {
_currentDB = noiseReading.meanDecibel;
});
},
onError: (error) {
print('오류: $error');
},
);
setState(() {
_isListening = true;
});
}
}
void _stopListening() {
_noiseSubscription?.cancel();
setState(() {
_isListening = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('데시벨 측정기'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${_currentDB.toInt()} dB',
style: TextStyle(fontSize: 48),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _isListening ? _stopListening : _startListening,
child: Text(_isListening ? '정지' : '시작'),
),
],
),
),
);
}
}
코드 구조 분석
핵심 클래스들
// 📊 데시벨 읽기
NoiseMeter().noise.listen((NoiseReading reading) {
double db = reading.meanDecibel; // 평균 데시벨
});
// 🔒 권한 요청
Permission.microphone.request();
// 🎛️ 스트림 제어
StreamSubscription<NoiseReading>? subscription;
상태 관리
class _DecibelMeterState extends State<DecibelMeter> {
double _currentDB = 0.0; // 현재 데시벨 값
bool _isListening = false; // 측정 중인지 상태
StreamSubscription<NoiseReading>? _noiseSubscription; // 스트림 구독 객체
}
동작 흐름
- 앱 시작
- 마이크 권한 요청
- 권한 허용 시 NoiseMeter 시작
- 실시간 데시벨 스트림 수신
- UI 업데이트 (setState)
- 정지 버튼으로 스트림 취소
주요 학습 포인트
1. 스트림(Stream) 패턴
// 스트림 구독
_subscription = NoiseMeter().noise.listen(
(data) => setState(() => _currentDB = data.meanDecibel),
onError: (error) => print('에러: $error'),
);
// 메모리 누수 방지
_subscription?.cancel();
2. 권한 관리
Future<void> _requestPermission() async {
var status = await Permission.microphone.request();
if (status.isGranted) {
// 권한 허용됨
} else if (status.isDenied) {
// 권한 거부됨
} else if (status.isPermanentlyDenied) {
// 영구 거부됨 - 설정으로 유도
openAppSettings();
}
}
3. 생명주기 관리
@override
void dispose() {
_noiseSubscription?.cancel(); // 위젯 해제 시 스트림 정리
super.dispose();
}
데시벨 참고값
| 데시벨(dB) | 소음 수준 | 예시 |
|---|---|---|
| 0-20 | 매우 조용 | 도서관, 속삭임 |
| 20-40 | 조용 | 조용한 사무실 |
| 40-60 | 보통 | 일반 대화 |
| 60-80 | 시끄러움 | TV, 음악 |
| 80-100 | 매우 시끄러움 | 지하철, 공사장 |
| 100+ | 위험 | 콘서트, 제트기 |
UI 개선 아이디어
데시벨 수준별 색상
Color _getDecibelColor(double db) {
if (db < 40) return Colors.green; // 조용
if (db < 70) return Colors.yellow; // 보통
return Colors.red; // 시끄러움
}
프로그레스 바
LinearProgressIndicator(
value: _currentDB / 120.0, // 120dB 기준
backgroundColor: Colors.grey[300],
valueColor: AlwaysStoppedAnimation(_getDecibelColor(_currentDB)),
)
트러블슈팅
권한 문제
// 권한 상태 확인
var status = await Permission.microphone.status;
if (status.isPermanentlyDenied) {
// 사용자를 설정으로 유도
showDialog(...);
}
플랫폼별 이슈
- Android:
RECORD_AUDIO권한 필수 - iOS:
Info.plist에 사용 목적 명시 필수 - Windows/Web:
noise_meter패키지 미지원
성능 최적화
// 백그라운드에서 정지
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
_stopListening(); // 배터리 절약
}
}
플랫폼 지원 현황
| 플랫폼 | 지원 여부 | 이유 |
|---|---|---|
| Android | ✅ | 완전 지원 |
| iOS | ✅ | 완전 지원 |
| Windows | ❌ | noise_meter 미지원 |
| Web | ❌ | 마이크 API 제한 |
| macOS | ❓ | 테스트 필요 |
| Linux | ❓ | 테스트 필요 |
다음 단계 학습
중급 기능
- 📈 실시간 그래프 (charts_flutter)
- 💾 측정값 저장 (shared_preferences)
- 🔔 임계값 알림 (flutter_local_notifications)
고급 기능
- 🎵 주파수 분석 (FFT)
- 📊 통계 분석 (평균, 최대, 최소)
- 🌐 데이터 공유 (Firebase)
완성된 기능
- ✅ 실시간 데시벨 측정
- ✅ 마이크 권한 관리
- ✅ 간단한 시작/정지 제어
- ✅ 크로스 플랫폼 (Android/iOS)
- ✅ 메모리 안전 (스트림 정리)
이 프로젝트로 Flutter의 스트림, 권한, 네이티브 패키지 사용법을 모두 학습할 수 있습니다! 🎉