본문으로 이동

플러터:마이크 입력: 두 판 사이의 차이

학교의 모든 지식. SMwiki
새 문서: {{플러터}} == 개요 == 기기에서 마이크를 사용하는 법. == 사전준비 권한 설정 == {| class="wikitable" |+ !항목 !설명 !비고 |- |안드로이드 |android/app/src/main/AndroidManifest.xml 에 넣는다. |<uses-permission android:name="android.permission.RECORD_AUDIO"/> |- |아이폰 |ios/Runner/Info.plist에 넣는다. |<key>NSMicrophoneUsageDescription</key> <string>앱에서 음성 입력을 사용합니다.</string> |- |패키지 설치 |dependenci...
 
 
(같은 사용자의 중간 판 2개는 보이지 않습니다)
3번째 줄: 3번째 줄:
== 개요 ==
== 개요 ==
기기에서 마이크를 사용하는 법.
기기에서 마이크를 사용하는 법.
== 플랫폼 지원 현황 ==
noise_meter를 이용할건데, 모바일에서만 사용 가능하다.
{| class="wikitable"
! 플랫폼 !! 지원 여부 !! 이유
|-
| '''Android''' || ✅ || 완전 지원
|-
| '''iOS''' || ✅ || 완전 지원
|-
| '''Windows''' || ❌ || noise_meter 미지원
|-
| '''Web''' || ❌ || 마이크 API 제한
|-
| '''macOS''' || ❓ ||
|-
| '''Linux''' || ❓ ||
|}


== 사전준비 권한 설정 ==
== 사전준비 권한 설정 ==
=== 권한 부여 ===
{| class="wikitable"
{| class="wikitable"
|+
!항목
!항목
!설명
!설명
13번째 줄: 32번째 줄:
|안드로이드
|안드로이드
|android/app/src/main/AndroidManifest.xml 에 넣는다.
|android/app/src/main/AndroidManifest.xml 에 넣는다.
|<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
* 권한은 <code><application></code> 태그 밖, <code><manifest></code> 태그 안에 위치해야 함.
* <uses-permission android:name="android.permission.RECORD_AUDIO"/>
|-
|-
|아이폰
|아이폰
19번째 줄: 40번째 줄:
|<key>NSMicrophoneUsageDescription</key>
|<key>NSMicrophoneUsageDescription</key>
<string>앱에서 음성 입력을 사용합니다.</string>
<string>앱에서 음성 입력을 사용합니다.</string>
iOS에선 메시지도 직접 입력할 수 있음.(여기에 사용 목적 명시해야 함.)
|-
|Windows,
Web
|별도 설정 불필요. 자동으로 팝업이 뜸.
|
|}
=== 필요한 패키지 점검 ===
{| class="wikitable"
!항목
!설명
!비고
|-
|-
|패키지 설치
|패키지 설치
|dependencies:
|# pubspec.yaml
dependencies:


  flutter:
  flutter:
27번째 줄: 63번째 줄:
    sdk: flutter
    sdk: flutter


  permission_handler: ^10.5.0
permission_handler: ^11.3.1


  noise_meter: ^1.1.0
noise_meter: ^5.0.1
|noise_meter는 db를 읽는다.
|noise_meter는 db를 읽는다.
아마 시간이 지나면 오래된 버전이라 문제가 발생할 수 있음.
flutter pub get
|}
|}
== 동작 흐름 ==
# 앱 시작
# 마이크 권한 요청
# 권한 허용 시 NoiseMeter 시작
# 실시간 데시벨 스트림 수신
# UI 업데이트 (setState)
# 정지 버튼으로 스트림 취소


== 데시벨 측정 예시 코드 ==
== 데시벨 측정 예시 코드 ==
마찬가지로 패키지 버전에 따라 함수명이 달라지기도 함. 오래되면 문제가 발생할 수 있음.<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 'dart:async';


import 'package:noise_meter/noise_meter.dart';
void main() => runApp(MyApp());
 
void main() {
 
  runApp(const MyApp());


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '데시벨 측정기',
      home: DecibelMeter(),
    );
  }
}
}


class MyApp extends StatefulWidget {
class DecibelMeter extends StatefulWidget {
 
  @override
  const MyApp({super.key});
  _DecibelMeterState createState() => _DecibelMeterState();
 
  @override
 
  State<MyApp> createState() => _MyAppState();
 
}
}


class _MyAppState extends State<MyApp> {
class _DecibelMeterState extends State<DecibelMeter> {
  double _currentDB = 0.0;
  bool _isListening = false;
  StreamSubscription<NoiseReading>? _noiseSubscription;


  NoiseMeter? _noiseMeter;
  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;
      });
    }
  }


  StreamSubscription<NoiseReading>? _subscription;
  void _stopListening() {
    _noiseSubscription?.cancel();
    setState(() {
      _isListening = false;
    });
  }


  double _decibel = 0;
  @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 ? '정지' : '시작'),
            ),
          ],
        ),
      ),
    );
  }
}</syntaxhighlight>


  @override
== 코드 구조 분석 ==


  void initState() {
=== 핵심 클래스들 ===
<syntaxhighlight lang="dart">
// 📊 데시벨 읽기
NoiseMeter().noise.listen((NoiseReading reading) {
  double db = reading.meanDecibel; // 평균 데시벨
});


    super.initState();
// 🔒 권한 요청
Permission.microphone.request();


    _initMicrophone();
// 🎛️ 스트림 제어
StreamSubscription<NoiseReading>? subscription;
</syntaxhighlight>


  }
=== 상태 관리 ===
<syntaxhighlight lang="dart">
class _DecibelMeterState extends State<DecibelMeter> {
  double _currentDB = 0.0;                      // 현재 데시벨 값
  bool _isListening = false;                    // 측정 중인지 상태
  StreamSubscription<NoiseReading>? _noiseSubscription;  // 스트림 구독 객체
}
</syntaxhighlight>


  Future<void> _initMicrophone() async {
== 주요 학습 포인트 ==


    // 권한 요청
=== 1. 스트림(Stream) 패턴 ===
<syntaxhighlight lang="dart">
// 스트림 구독
_subscription = NoiseMeter().noise.listen(
  (data) => setState(() => _currentDB = data.meanDecibel),
  onError: (error) => print('에러: $error'),
);


    if (await Permission.microphone.request().isGranted) {
// 메모리 누수 방지
_subscription?.cancel();
</syntaxhighlight>


      _noiseMeter = NoiseMeter(onError: (e) {
=== 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>


        print("Noise meter error: $e");
=== 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+ || 위험 || 콘서트, 제트기
|}


      _subscription = _noiseMeter!.noiseStream.listen((noise) {
== UI 개선 아이디어 ==


        setState(() {
=== 데시벨 수준별 색상 ===
 
<syntaxhighlight lang="dart">
          _decibel = noise.meanDecibel;
Color _getDecibelColor(double db) {
 
  if (db < 40) return Colors.green;     // 조용
        });
  if (db < 70) return Colors.yellow;     // 보통
 
  return Colors.red;                     // 시끄러움
      });
}
 
</syntaxhighlight>
      _noiseMeter!.start();
 
    } else {
 
      print("마이크 권한 거부됨");
 
    }
 
  }
 
  @override
 
  void dispose() {
 
    _subscription?.cancel();
 
    _noiseMeter?.stop();
 
    super.dispose();
 
  }
 
  @override
 
  Widget build(BuildContext context) {
 
    return MaterialApp(
 
      home: Scaffold(
 
        appBar: AppBar(title: const Text("데시벨 측정기")),
 
        body: Center(
 
          child: Text(
 
            "${_decibel.toStringAsFixed(1)} dB",
 
            style: const TextStyle(fontSize: 50),


          ),
=== 프로그레스 바 ===
<syntaxhighlight lang="dart">
LinearProgressIndicator(
  value: _currentDB / 120.0, // 120dB 기준
  backgroundColor: Colors.grey[300],
  valueColor: AlwaysStoppedAnimation(_getDecibelColor(_currentDB)),
)
</syntaxhighlight>


        ),
== 다음 단계 학습 ==


      ),
=== 중급 기능 ===
* 📈 실시간 그래프 (charts_flutter)
* 💾 측정값 저장 (shared_preferences)
* 🔔 임계값 알림 (flutter_local_notifications)


    );
=== 고급 기능 ===
* 🎵 주파수 분석 (FFT)
* 📊 통계 분석 (평균, 최대, 최소)
* 🌐 데이터 공유 (Firebase)


  }
== 완성된 기능 ==
* ✅ 실시간 데시벨 측정
* ✅ 마이크 권한 관리
* ✅ 간단한 시작/정지 제어
* ✅ 크로스 플랫폼 (Android/iOS)
* ✅ 메모리 안전 (스트림 정리)


}
이 프로젝트로 '''Flutter의 스트림, 권한, 네이티브 패키지 사용법'''을 모두 학습할 수 있습니다! 🎉

2025년 11월 19일 (수) 07:19 기준 최신판

틀:플러터 Dart:개요 플러터에 대한 지식 분류

  1. 플러터:개요
    1. 플러터:VSCode
    2. 플러터:안드로이드 스튜디오
  2. 플러터:실행
  3. 플러터:개념 잡기
    1. 플러터:화면 하나 만들기
    2. 플러터:변하는 화면(StatefulWidget)
    3. 플러터:화면 전환(화면 쌓기, 하단 네비게이션 바)
    4. 플러터:화면 전환(Drawer)
    5. 플러터:입력 관련
      1. 플러터:버튼
      2. 플러터:키보드 입력
      3. 플러터:슬라이더
    6. 플러터:그래프 그리기(fl chart)
    7. 플러터:데이터 저장(간단한 데이터)
    8. 플러터:인증(Firebase 인증)(미완)
    9. 플러터:인증(OAuth2)(미완)
  4. 권한 사용
    1. 플러터:마이크 입력
  5. 위젯
    1. 플러터:아이콘
    2. 플러터:레이아웃 계열 위젯
    3. 플러터:네비게이션 계열 위젯
    4. 플러터:버튼
    5. 플러터:상태관리(미완)
  6. 플러터:DB연결
    1. 플러터:Firebase(미완)
    2. 플러터:MySQL(미완)
  7. 디자인
    1. 플러터:테마
    2. 플러터:앱바
  8. 플러터:배포
    1. 플러터:배포(안드로이드)(미완)
  9. 플러터:참고자료
  10. 플러터:위젯
    1. 플러터:공간배치용 위젯
  11. 플러터:구글 AdMob(미완)
  12. 플러터:라이브러리
    1. 플러터:logger

기기에서 마이크를 사용하는 법.

플랫폼 지원 현황

[편집 | 원본 편집]

noise_meter를 이용할건데, 모바일에서만 사용 가능하다.

플랫폼 지원 여부 이유
Android 완전 지원
iOS 완전 지원
Windows noise_meter 미지원
Web 마이크 API 제한
macOS
Linux

사전준비 권한 설정

[편집 | 원본 편집]

권한 부여

[편집 | 원본 편집]
항목 설명 비고
안드로이드 android/app/src/main/AndroidManifest.xml 에 넣는다.
  • 권한은 <application> 태그 밖, <manifest> 태그 안에 위치해야 함.
  • <uses-permission android:name="android.permission.RECORD_AUDIO"/>
아이폰 ios/Runner/Info.plist에 넣는다. <key>NSMicrophoneUsageDescription</key>

<string>앱에서 음성 입력을 사용합니다.</string>

iOS에선 메시지도 직접 입력할 수 있음.(여기에 사용 목적 명시해야 함.)

Windows,

Web

별도 설정 불필요. 자동으로 팝업이 뜸.

필요한 패키지 점검

[편집 | 원본 편집]
항목 설명 비고
패키지 설치 # pubspec.yaml

dependencies:

  flutter:

    sdk: flutter

permission_handler: ^11.3.1

noise_meter: ^5.0.1

noise_meter는 db를 읽는다.

아마 시간이 지나면 오래된 버전이라 문제가 발생할 수 있음.

flutter pub get

동작 흐름

[편집 | 원본 편집]
  1. 앱 시작
  2. 마이크 권한 요청
  3. 권한 허용 시 NoiseMeter 시작
  4. 실시간 데시벨 스트림 수신
  5. UI 업데이트 (setState)
  6. 정지 버튼으로 스트림 취소

데시벨 측정 예시 코드

[편집 | 원본 편집]

마찬가지로 패키지 버전에 따라 함수명이 달라지기도 함. 오래되면 문제가 발생할 수 있음.

// 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;  // 스트림 구독 객체
}

주요 학습 포인트

[편집 | 원본 편집]

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)),
)

다음 단계 학습

[편집 | 원본 편집]

중급 기능

[편집 | 원본 편집]
  • 📈 실시간 그래프 (charts_flutter)
  • 💾 측정값 저장 (shared_preferences)
  • 🔔 임계값 알림 (flutter_local_notifications)

고급 기능

[편집 | 원본 편집]
  • 🎵 주파수 분석 (FFT)
  • 📊 통계 분석 (평균, 최대, 최소)
  • 🌐 데이터 공유 (Firebase)

완성된 기능

[편집 | 원본 편집]
  • ✅ 실시간 데시벨 측정
  • ✅ 마이크 권한 관리
  • ✅ 간단한 시작/정지 제어
  • ✅ 크로스 플랫폼 (Android/iOS)
  • ✅ 메모리 안전 (스트림 정리)

이 프로젝트로 Flutter의 스트림, 권한, 네이티브 패키지 사용법을 모두 학습할 수 있습니다! 🎉