آموزش برنامه نویسی اندروید با اندروید استودیو (بخش چهل و ششم: طراحی اپلیکیشن ضبط صدا با استفاده از Media Recorder )

استاندارد

در بخش قبلی در مورد Audio Capture با استفاده Media Recorder توضیحاتی داده شد.

ابتدا در مورد طراحی لایه اپلیکیشن برای Audio Capturing توضیح داده شد. سه دکمه برای ضبط صدا ، توقف ضبط صدا و همچنین پخش صوت ضبط شده به لایه اپلیکیشن اضافه کردیم.

سپس در قسمت کد جاوای برنامه گفته شد که برای ضبط صدا در اپلیکیشن اندرویدی به کلاس Media Recorder نیاز داریم.

با استفاده از شی که از کلاس Media Recorder ایجاد می کردیم، در رویداد های مختلف دکمه های (Button ها ) ، می توانستیم اقدام به ضبط صدا و یا پخش آن کنیم.

همچنین برای صوتی که ضبط می کردیم، احتیاج به کدهایی داشتیم که آن ها را در منبعی از حافظه دستگاه اندرویدی، ذخیره نماییم.

بخش قبلی خلاصه ای از قسمت های مختلف این کارها را توضیح می داد. ولی پروژه را به صورت عملی اجرا نکردیم.

پس در این بخش به توضیحی کاملتر در این باره می پردازیم تا یک پروژه ی ضبط صدا را با هم برنامه نویسی کنیم.

برای پخش صوت، باید از کلاس Media Player که در بخش بیست و هفتم در مورد آن توضیح داده شد، استفاده نماییم.

در ادامه با Gsm Developers همراه باشید تا به راحتی بتوانید یک پروژه ی ضبط صدای اپلیکیشن اندرویدی بنویسید.

ابتدا یک پروژه با نام Media-Recorder-Gsm در اندروید استودیو ایجاد می کنیم.

در اولین مرحله باید به سراغ قسمت طراحی اپلیکیشن برویم.

طراحی لایه اپلیکیشن

در پروژه ایجاد شده، به قسمت Layout یعنی همان لایه برنامه می روم و فایل activity_main.xml را باز می کنم.

از قسمت جعبه ابزار ( Palette ) ها ، چهار Button را به داخل لایه اپلیکیشن برنامه می کشم.

نام یکی از دکمه ها را Start ، نام دیگری را Stop و نام سومین دکمه را Play و نام چهارمین دکمه را Stop Playing می گذارم.

در اینجا یک دکمه ی اضافی به منظور قطع پخش صوت ضبط شده، نیز نسبت به قبل اضافه کردیم.

نمای فعلی لایه اپلیکیشن ما همراه دکمه های اضافه شده، به صورت زیر است:

دقت نمایید که مشخصه ی android:layout_width در دکمه های Play و Stop Playing ، به صورت match_parent است.

همانند بخش گذشته برای راحتی کار در قسمت کد نویسی، آیدی دکمه Start را StartButton و آیدی دکمه Stop را StopButton و آیدی دکمه Play را PlayButton می گذاریم.

آیدی دکمه Stop Playing را نیز StopPlayingBtn می گذاریم.

کدهای xml چهار Buttonی که من اضافه نمودم:

<Button
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginLeft="50dp"
        android:layout_marginStart="36dp"
        android:layout_marginBottom="194dp"
        android:id="@+id/StartButton" />

    <Button
        android:text="Stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/StopButton"
        android:layout_alignBaseline="@+id/StartButton"
        android:layout_alignBottom="@+id/StartButton"
        android:layout_toRightOf="@+id/imageView"
        android:layout_toEndOf="@+id/imageView" />

    <Button
        android:text="Play"
        android:layout_height="wrap_content"
        android:layout_marginTop="46dp"
        android:id="@+id/PlayButton"
        android:layout_width="match_parent"
        android:layout_below="@+id/StopButton"
        android:layout_centerHorizontal="true" />

    <Button
        android:text="Stop Playing"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="13dp"
        android:id="@+id/StopPlayingBtn"
        android:layout_below="@+id/PlayButton"
        android:layout_centerHorizontal="true" />

برای پروژه ای که ایجاد کردیم یک ImageView به شکل میکروفون، به لایه اپلیکیشن اضافه کنیم.

برای این کار به قسمت Paletteها در سمت چپ لایه اپلیکیشن رفته، سپس یک ImageView را به داخل لایه اپلیکیشن برنامه می کشم.

پنجره ی زیر باز می شود و من presence_audio_online را انتخاب می کنم.

کد xml از ImageView که اضافه نمودم:

<ImageView
        android:layout_width="100px"
        android:layout_height="100px"
        app:srcCompat="@android:drawable/presence_audio_online"
        android:layout_alignParentTop="true"
        android:layout_marginTop="75dp"
        android:layout_marginLeft="150dp"
        android:id="@+id/imageView" />

محتویات فایل xml لایه اپلیکیشن من به شکل زیر است:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.gsm_developers.media_recorder_gsm.MainActivity">

    <ImageView
        android:layout_width="100px"
        android:layout_height="100px"
        app:srcCompat="@android:drawable/presence_audio_online"
        android:layout_alignParentTop="true"
        android:layout_marginTop="75dp"
        android:layout_marginLeft="150dp"
        android:id="@+id/imageView" />

    <Button
        android:text="Start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginLeft="50dp"
        android:layout_marginStart="36dp"
        android:layout_marginBottom="194dp"
        android:id="@+id/StartButton" />

    <Button
        android:text="Stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/StopButton"
        android:layout_alignBaseline="@+id/StartButton"
        android:layout_alignBottom="@+id/StartButton"
        android:layout_toRightOf="@+id/imageView"
        android:layout_toEndOf="@+id/imageView" />

    <Button
        android:text="Play"
        android:layout_height="wrap_content"
        android:layout_marginTop="46dp"
        android:id="@+id/PlayButton"
        android:layout_width="match_parent"
        android:layout_below="@+id/StopButton"
        android:layout_centerHorizontal="true" />

    <Button
        android:text="Stop Playing"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="13dp"
        android:id="@+id/StopPlayingBtn"
        android:layout_below="@+id/PlayButton"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

کار در طراحی لایه اپلیکیشن به اتمام می رسد.

کد جاوای برنامه

پس از اتمام قسمت لایه اپلیکیشن به سراغ قسمت جاوای برنامه  می رویم، تا کدهای جاوا برنامه را بنویسیم.

برای این کار، فایل MainActivity.java را باز کرده و مرحله به مرحله کدهای مورد نظرمان را می نویسیم.

ابتدا در کلاس MainActivity  (خارج از متد onCreate ) چهار شی از کلاس Button ایجاد می کنیم تا دکمه هایی Buttonی که در بخش لایه ها تعریف کردیم را بعدا به کد جاوای برنامه معرفی کنیم.

private Button startBtn,stopBtn,playBtn,stopPlayingBtn;

دو شی به نام های mediaRecorder و mediaPlayer برای از کلاس های MediaRecorder و MediaPlayer برای ضبط و پخش صوت ، تعریف می کنم.

MediaRecorder mediaRecorder;
MediaPlayer mediaPlayer;

یک رشته برای محل ذخیره سازی فایل صوتی در دستگاه اندرویدی، تعریف می کنم.

String AudioSavePathInDevice = null;

همچنین یک متغیر از نوع int برای بررسی دسترسی به میکروفون، نیز تعریف می کنم.

public static final int RequestPermissionCode = 1;

از تمامی این موارد بعدا در کد استفاده خواهیم کرد.

پس تا به حال کدهای زیر را در کلاس MainActivity و بالاتر از متد onCreate تعریف نمودم:

private Button startBtn,stopBtn,playBtn,stopPlayingBtn;
MediaRecorder mediaRecorder;
MediaPlayer mediaPlayer;
String AudioSavePathInDevice = null;
public static final int RequestPermissionCode = 1;

به داخل متد ()onCreate می رویم و کدهای زیر را به آن اضافه می کنیم تا وقتی اپلیکیشن بالا بیاید، دکمه ها برای آن تعریف شود.

startBtn = (Button) findViewById(R.id.StartButton);
stopBtn = (Button) findViewById(R.id.StopButton);
playBtn = (Button) findViewById(R.id.PlayButton);
stopPlayingBtn = (Button)findViewById(R.id.StopPlayingBtn);

بهتر است در ابتدا وقتی اپلیکیشن شروع به کار میکند، دکمه های پخش صوت، قطع پخش صوت و قطع عملیات ضبط غیر فعال باشند.

برای این کار کد های زیر را در داخل متد ()onCreate تعریف می کنیم:

stopBtn.setEnabled(false);
playBtn.setEnabled(false);
stopPlayingBtn.setEnabled(false);

باید برای دکمه ی Start که عملیات ضبط صدا را انجام می دهد، رویدادی تعریف کنیم.

startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(checkPermission()) {

                    AudioSavePathInDevice =
                            Environment.getExternalStorageDirectory().getAbsolutePath() + "/AudioRecording.3gp";

                    MediaRecorderReady();

                    try {
                        mediaRecorder.prepare();
                        mediaRecorder.start();
                    } catch (IllegalStateException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    startBtn.setEnabled(false);
                    stopBtn.setEnabled(true);

                    Toast.makeText(MainActivity.this, "Recording started",
                            Toast.LENGTH_LONG).show();
                } else {
                    requestPermission();
                }

            }
        });

ابتدا این رویداد با استفاده از متد checkPermision دسترسی اپلیکیشن به میکروفون و حافظه ی خارجی را بررسی می نماید. این متد بعدا توضیح داده می شود.

در قسمت بعدی، محل ذخیره ی صدای ضبط شده را به متغیر رشته ای که به نام AudioSavePathInDevice در ابتدا تعریف نمودیم، نسبت می دهد.

تابع MediaRecorderReady در این بخش، تعریف شده است تا کارهایی را انجام دهد که دیگر نیاز به تکرار آن ها نباشد. زیرا در بخش چهل و پنجم، آن عملیات مدام در متدهای برنامه تکرار می شد. اما آن عملیات چیست؟

در واقع این تابع همان عملیات مربوط به فرمت خروجی صدای پخش شده را انجام می دهد که در بخش قبلی این عملیات تعریف شدند ولی در این بخش این عملیات را برای راحتی کار و مرتب بودن اپلیکیشن در یک متد دیگر می نویسیم.

پس خارج از متد ()onCreate این تابع را تعریف نمایید:

public void MediaRecorderReady(){
        mediaRecorder=new MediaRecorder();
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mediaRecorder.setAudioEncoder(MediaRecorder.OutputFormat.AMR_NB);
        mediaRecorder.setOutputFile(AudioSavePathInDevice);
}

در خط اول، شی mediaRecorder تعریف می شود. سپس در خط دوم سورس Audio ، در خط سوم فرمت خروجی Audio و در خط چهارم روش انکود کردن Audio انجام می شود.

در خط پنجم ذخیره ی Audio ی ضبط شده بر روی متغیر AudioSavePathInDevice که در مرحله قبلی تعریف نمودیم انجام می شود.

پس تا الان کدهای قسمت MainActivity ما باید به شکل زیر باشد.

اجازه دهید ابتدا کدهایی برای رویداد دکمه های Stop ، Play و StopPlaying تعریف کنیم. سپس در آخر به قسمت CheckPermission می پردازیم.

کد Stop برای قطع عملیات پخش چیز پیچیده ای ندارد.

stopBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mediaRecorder.stop();
                stopBtn.setEnabled(false);
                playBtn.setEnabled(true);
                startBtn.setEnabled(true);
                stopPlayingBtn.setEnabled(false);

                Toast.makeText(MainActivity.this, "Recording Completed",
                        Toast.LENGTH_LONG).show();
            }
        });

ابتدا با استفاده از متد mediaRecorder.stop عملیات پخش قطع می شود. سپس دکمه ی Stop و Stop Playing غیر فعال و دکمه های Start و Play فعال می شوند.

در آخر با استفاده از یک Toast پیغامی مبنی بر موفق بودن عملیات ضبط نشان داده می شود.

برای رویداد دکمه ی Play کد زیر را بنویسید.

playBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) throws IllegalArgumentException,
                SecurityException, IllegalStateException {

                stopBtn.setEnabled(false);
                startBtn.setEnabled(false);
                stopPlayingBtn.setEnabled(true);

                mediaPlayer = new MediaPlayer();
                try {
                    mediaPlayer.setDataSource(AudioSavePathInDevice);
                    mediaPlayer.prepare();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                mediaPlayer.start();
                Toast.makeText(MainActivity.this, "Recording Playing",
                        Toast.LENGTH_LONG).show();
            }
        });

این کد، دکمه های Start و Stop را غیرفعال می کند. سپس دکمه Stop Playing را فعال می کند. تا اگر بخواهیم عملیات توقف پخش صورت پذیرد.

یک شی از نوع MediaPlayer ایجاد می شود تا عملیات پخش صوت انجام گیرد.

در قسمت میانی کد بالا، کدهای زیر را مشاهده می کنید. این کد ها مربوط به عملیات پخش صوت از حافظه ی دستگاه با استفاده از آدرسی که توسط رشته ی AudioSavePathInDevice تعریف نمودیم، را انجام می دهند.

mediaPlayer = new MediaPlayer();
                try {
                    mediaPlayer.setDataSource(AudioSavePathInDevice);
                    mediaPlayer.prepare();
                } catch (IOException e) {
                    e.printStackTrace();
                }

عملیات پخش توسط شی Media Player انجام می پذیرد.

پس یک شی از کلاس Media Player باید ایجاد نماییم.

متد mediaPlayer.start عملیات پخش صوت را انجام می دهد.

با استفاده از پیغام Toast موفق بودن عملیات Recording Playing به کاربر نشان داده می شود.

stopPlayingBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                stopBtn.setEnabled(false);
                startBtn.setEnabled(true);
                stopPlayingBtn.setEnabled(false);
                playBtn.setEnabled(true);

                if(mediaPlayer != null){
                    mediaPlayer.stop();
                    mediaPlayer.release();
                    MediaRecorderReady();
                }
            }
        });

در آخر به رویدادی که برای دکمه ی stopPlayingBtn تعریف نمودیم، می پردازیم.

این رویداد، دکمه های Stop و Stop Playing را غیرفعال، و دکمه های Start و Play را فعال می کند. تا امکان ضبط و پخش دوباره را داشته باشیم.

سپس با استفاده از متدهای mediaplayer.stop و mediaplayer.release عملیات پخش را قطع و حافظه ی تخصیص داده شده به آن را آزاد می کند.

بررسی Permission ها

این برنامه، برای خواندن و نوشتن صوت بر روی حافظه ی دستگاه، و همچنین میکروفون دستگاه باید دسترسی های لازم را داشته باشد.

برای این کار به سراغ AndroidManifest.xml در app -> manifests بروید. سپس کد زیر را قبل از تگ application> اضافه نمایید.

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

محتویات فایل AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.gsm_developers.media_recorder_gsm">
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

در MainActivity.java ، کدهای زیر را در بالای این فایل اضافه نمایید تا دسترسی هایی که دادیم، به قسمت جاوای برنامه Import شوند.

import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;

در آخر کدهای زیر را اضافه نمایید تا اپلیکیشن  بتواند عملیات دسترسی دادن را از کاربر بخواهند و سپس با استفاده از فرمان کاربر اقدام به بررسی این عملیات بکند.

این توابع با استفاده از پیغام هایی از کاربر برای دسترسی به حافظه ی داخلی و میکروفون ، اجازه می خواهد

برای این کار از کاربر می خواهد که allow و یا Deny را انتخاب نماید.

پس از آن که کاربر Allow را انتخاب نماید، این توابع با بررسی دسترسی داده شده به اپلیکیشن، در صورت مثبت بودن، به مرحله ی بعدی می رود.

اگر یادتان باشد، رویدادی که برای دکمه ی Start تعریف کرده بودیم از این تابع در قسمت شرطی خود یعنی if استفاده می کرد و در صورت موفقیت آمیز بودن، بدنه ی این رویداد اجرا می شد.

این گونه دسترسی دادن به اپلیکیشن برای درخواست از کاربر، در بخشی به صورت جداگانه بررسی می شود.

    private void requestPermission() {
        ActivityCompat.requestPermissions(MainActivity.this, new
                String[]{WRITE_EXTERNAL_STORAGE, RECORD_AUDIO}, RequestPermissionCode);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,String permissions[], int[] grantResults) {
        switch (requestCode) {
            case RequestPermissionCode:
                if (grantResults.length> 0) {
                    boolean StoragePermission = grantResults[0] ==
                            PackageManager.PERMISSION_GRANTED;
                    boolean RecordPermission = grantResults[1] ==
                            PackageManager.PERMISSION_GRANTED;

                    if (StoragePermission && RecordPermission) {
                        Toast.makeText(MainActivity.this, "Permission Granted",
                                Toast.LENGTH_LONG).show();
                    } else {
                        Toast.makeText(MainActivity.this,"Permission Denied",Toast.LENGTH_LONG).show();
                    }
                }
                break;
        }
    }

    public boolean checkPermission() {
        int result = ContextCompat.checkSelfPermission(getApplicationContext(),
                WRITE_EXTERNAL_STORAGE);
        int result1 = ContextCompat.checkSelfPermission(getApplicationContext(),
                RECORD_AUDIO);
        return result == PackageManager.PERMISSION_GRANTED &&
                result1 == PackageManager.PERMISSION_GRANTED;
    }

می توانستیم مسئله را ساده تر نیز انجام بدیم، و با دسترسی دادن فقط به اپلیکیشن ، این پروژه قادر به ضبط صدا و ذخیره آن باشد.

برای مشاهده ی کلیه ی کدهای فایل MainActivity.java کلیک نمایید.

اپلیکیشن را اجرا می کنیم.

در ابتدا برای ضبط صدا دکمه Start را می زنیم.

همان طور که انتظار می رفت، اپلیکیشن از ما می خواهد که اجازه دسترسی به حافظه ی دستگاه را بدهیم.

پس از زدن Allow ، این بار اپلیکیشن از ما می خواهد که اجازه ضبط صدا را بدهیم.

وقتی دسترسی های لازم به اپلیکیشن داده شد، یک پیغام Toast با نوشته ی Permission Granted به کاربر نشان داده می شود.

وقتی دکمه ی Start را می زنیم:

وقتی دکمه ی Stop را می زنیم، یک پیغام Toast با نوشته ی Recording Completed به کاربر نشان داده می شود.

قبل از این پیغام، صدای ضبط شده در حافظه ی دستگاه ذخیره می شود.

 

با زدن دکمه ی Play ، اپلیکیشن صدای ضبط شده را از حافظه ی دستگاه فراخوانی میکند.

سپس با استفاده از Media Player اقدام به پخش صدای ضبط شده می کند.