web-dev-qa-db-ja.com

Android Text-To-Speech APIサウンドロボット

私はAndroid開発を初めて学習しています。私の目標は、テキストを取り込んで読み上げるシンプルなHello Worldアプリケーションを作成することです。

私が見つけた例に基づいてコードを作成しましたが、これが私のコードです:

class MainFeeds : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main_feeds)



        card.setOnClickListener{
            Toast.makeText(this, "Hello", Toast.LENGTH_LONG).show()
            TTS(this, "Hello this is leo")
        }
    }

}


class TTS(private val activity: Activity,
          private val message: String) : TextToSpeech.OnInitListener {

          private val tts: TextToSpeech = TextToSpeech(activity, this, "com.google.Android.tts")

    override fun onInit(i: Int) {
        if (i == TextToSpeech.SUCCESS) {

            val localeUS = Locale.US

            val result: Int
            result = tts.setLanguage(localeUS)

            if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Toast.makeText(activity, "This Language is not supported", Toast.LENGTH_SHORT).show()
            } else {
                speakOut(message)
            }

        } else {
            Toast.makeText(activity, "Initilization Failed!", Toast.LENGTH_SHORT).show()
        }
    }

    private fun speakOut(message: String) {
        tts.speak(message, TextToSpeech.QUEUE_FLUSH, null, null)
    }
}

そして、それは完璧に機能します。私が遭遇している問題は、シンセサイザーから出力されるオーディオが、Googleマップを使用していてインターネットから切断されたときのように、非常にロボットのように聞こえることです。音声Googleアシスタントを使用すると、有効にする必要がある他のAPIを利用できますか?

編集:私はpixel 2xlでアプリを実行してみましたが、Googleアシスタントの音声を使用していないため、ロボットのように聞こえます。

15
Stupid.Fat.Cat

音声の品質は、何よりもまず、作成したTextToSpeechオブジェクトで使用されている「音声エンジン」によって決まります。

private val tts: TextToSpeech = TextToSpeech(activity, this)

代わりに入力した場合:

private val tts: TextToSpeech = TextToSpeech(activity, this, "com.google.Android.tts")

次に、そのコードを実行するすべてのデバイスは、Google音声エンジンを使用するattemptを行いますが、デバイスに存在する場合にのみ実際に使用されます。

同様に、「com.samsung.SMT」を使用すると、Samsung音声エンジンを使用しようとします(これも高品質ですが、通常はSamsung [実際の]デバイスにのみインストールされます)。

Googleスピーチエンジンが利用可能かどうかは、AndroidデバイスのAPIレベル(Googleエンジンを実行するのに十分最近のものである限り))にはあまり依存しませんが、実際のGoogleテキスト読み上げエンジンがデバイスにインストールされています。

Googleエンジンがインストールされていることを確認するには:

Android Studio Emulator:

新しいエミュレータを作成し、「ターゲット」列に「Google API」または「Google Play」があるシステムイメージを選択します。

実際のデバイス:

Playストアに移動して、 Googleスピーチエンジン をインストールします。

現在、「TTS Diagnostics」アプリを作成しており、準備ができたらGithub経由で投稿します。私は、Android(または少なくともその動作を予測しようとする)のTTSが本物の獣になる可能性があることを学びました。

もちろん、ドキュメントもお勧めします。 Java | Kotlin

2
Boober Bunz

この質問に答える小さなテストプログラムを作成しました。

それはあなたにグーグルエンジンがそれに持っているすべての声のリストを示して、あなたはそれらをクリックしてそれらを聞く!わーい!

それが実際に行うこと:

  • デバイスに存在する場合は、Googleの音声合成エンジンを使用してTextToSpeechオブジェクトを初期化します。
  • コードで指定されたロケール(この場合は英語)に対応し、インストールされているGoogleテキスト読み上げエンジンのバージョンに対応する、利用可能なすべての音声を含むListViewから特定の音声を選択できます。

このようにして、すべての音声をテストして、探している「Googleアシスタント」の音声がどこかにあるかどうかを確認できます。利用できない場合は、Googleの音声合成エンジンの新しいバージョンがリリースされたときに確認し続けることができます。このテストで最高品質の音声は両方ともquality:400であり、ネットワーク接続が必要であることを指定しているように思えます。

ノート:

  • ボイス(特に英語)は、「インストールされていない」場合でも、おそらく「再生」されます。これは、setVoice(Voice v)を使用すると、要求された音声が利用できない場合でも(Google)エンジンが「成功」intを返すためです(!)、他の「バックアップ」音声が手元にある限り。同じ言語の。残念ながら、これはすべてバックグラウンドで行われ、getVoice()を使用してオブジェクトを比較した場合でも、要求されたのとまったく同じ音声を使用しているとこっそり報告します。 :(。

  • 一般に、音声で「ISインストール済み」と表示されている場合、聞こえる音声は要求した音声です。

  • これらの理由により、これらの音声をテストするときはインターネットに接続していることを確認する必要があります(使用できない音声を要求すると自動的にインストールされるため)...また、ネットワーク接続を必要とする音声が発生しないようにする必要があります「自動ダウングレード」。

  • 音声のリストビューをスワイプ/更新して、音声がまだインストールされているかどうかを確認するか、システムのプルダウンメニューを使用してダウンロードを監視するか、デバイスのシステム設定でGoogleのテキスト読み上げの設定に移動します。

  • リストビューでは、「必要なネットワーク」や「インストール済み」などのVoice機能は、Googleエンジンが報告する内容の単なるエコーであり、正確ではない場合があります。 :(

  • Voice class documentation で指定されている最大可能な音声品質は500です。私のテストでは、品質400までの音声しか見つけることができませんでした。これは、最新バージョンのGoogle text-toがないためである可能性があります-テストデバイスにインストールされている音声(および更新するためのPlayストアアクセス権がありません)。実際のデバイスを使用している場合は、Google Playストアを使用してGoogle TTSの最新バージョンをインストールすることをお勧めします。ログでエンジンのバージョンを確認できます。ウィキペディアによると、この記事の執筆時点での最新バージョンは3.15.18.200023596です。テストデバイスのバージョンは3.13.1です。

このテストアプリを再作成するには、空のJavaプロジェクトAndroid Studioを最低21のAPIで作成します。(getVoices()は事前に機能しません) 21)。

マニフェスト:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
    package=" [ your.package.name ] "
    Android:windowSoftInputMode="stateHidden">

    <application
        Android:allowBackup="true"
        Android:icon="@mipmap/ic_launcher"
        Android:label="@string/app_name"
        Android:roundIcon="@mipmap/ic_launcher_round"
        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>

主な活動:

package [ your package name ];

import Android.content.Intent;
import Android.content.pm.PackageManager;
import Android.content.pm.ResolveInfo;
import Android.graphics.Color;
import Android.speech.tts.TextToSpeech;
import Android.speech.tts.UtteranceProgressListener;
import Android.speech.tts.Voice;
import Android.support.v4.widget.SwipeRefreshLayout;
import Android.support.v7.app.AppCompatActivity;
import Android.os.Bundle;
import Android.util.Log;
import Android.view.View;
import Android.view.inputmethod.InputMethodManager;
import Android.widget.AdapterView;
import Android.widget.EditText;
import Android.widget.ListView;
import Android.widget.Spinner;
import Android.widget.TextView;

import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.HashMap;
import Java.util.List;
import Java.util.Locale;

public class MainActivity extends AppCompatActivity {

    EditText textToSpeak;
    TextView progressView;
    TextToSpeech googleTTS;
    ListView voiceListView;
    SwipeRefreshLayout swipeRefreshLayout;
    Long timeOfSpeakRequest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textToSpeak = findViewById(R.id.textToSpeak);
        textToSpeak.setText("Do I sound robotic to you?  1,2,3,4... yabadabadoo.  "
                + "ooo! ahh! la-la-la-la-la!  num-num-dibby-dibby-num-tick-tock...  "
                + "Can I pronounce the Word, Antidisestablishmentarianism?  "
                + "Gerp!  My pants are too tight!  "
                + "CODE RED!  CODE RED!  Initiate disassemble!  Ice Cream is cold "
                + "...in my pants.  Exterminate!  exterminate!  Directive 4 is "
                + "classified."
        );
        progressView = findViewById(R.id.progressView);
        voiceListView = findViewById(R.id.voiceListView);
        swipeRefreshLayout = findViewById(R.id.swipeRefresh);


        // Create the TTS and wait until it's initialized to do anything else
        if (isGoogleEngineInstalled()) {
            createGoogleTTS();
        } else {
            Log.i("XXX", "onCreate(): Google not installed -- nothing done.");
        }

    }

    @Override
    protected void onStart() {
        super.onStart();

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                assignFullSetOfVoicesToVoiceListView();
            }
        });

    }

    // this is where the program really begins (when the TTS is initialized)
    private void onTTSInitialized() {

        setUpWhatHappensWhenAVoiceItemIsClicked();
        setUtteranceProgressListenerOnTheTTS();
        assignFullSetOfVoicesToVoiceListView();

    }

    // FACTORED/EXTRACTED METHODS ----------------------------------------------------------------
    // These are just pulled out to make onCreate() easier to read and the basic sequence
    // of events more obvious.

    private void createGoogleTTS() {

        googleTTS = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
            @Override
            public void onInit(int status) {
                if (status != TextToSpeech.ERROR) {
                    Log.i("XXX", "Google tts initialized");
                    onTTSInitialized();
                } else {
                    Log.i("XXX", "Internal Google engine init error.");
                }
            }
        }, "com.google.Android.tts");

    }

    private void setUpWhatHappensWhenAVoiceItemIsClicked() {
        voiceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Voice desiredVoice = (Voice) parent.getAdapter().getItem(position);
                // if (setting the desired voice is "successful")...
                // in the case of google engine, this does not necessarily mean the voice you
                // want will actually be used. :(
                if (googleTTS.setVoice(desiredVoice) == 0) {
                    Log.i("XXX", "Speech voice set to: " + desiredVoice.toString());
                    // TTS did may "auto-downgrade" voice selection
                    // due to internal reason such as no data
                    // Unfortunately it will not tell you, and there seems to be no
                    // way of checking whether the presently selected voice (getVoice()) "equals"
                    // the desired voice.
                    speak();
                }
            }
        });
    }

    private void setUtteranceProgressListenerOnTheTTS() {

        UtteranceProgressListener blurp = new UtteranceProgressListener() {

            @Override // MIN API 15
            public void onStart(String s) {
                long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest;
                Log.i("XXX", "progress.onStart() callback.  "
                        + timeSinceSpeakCall + " millis since speak() was called.");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressView.setTextColor(Color.GREEN);
                        progressView.setText("PROGRESS: STARTED");
                    }
                });
            }

            @Override // MIN API 15
            public void onDone(String s) {
                long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest;
                Log.i("XXX", "progress.onDone() callback.  "
                        + timeSinceSpeakCall + " millis since speak() was called.");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressView.setTextColor(Color.GREEN);
                        progressView.setText("PROGRESS: DONE");
                    }
                });
            }

            // Getting an error can simply mean that the particular voice is not available
            // to the device yet... and still needs to be downloaded / is still downloading
            @Override // MIN API 15 (depracated at API 21)
            public void onError(String s) {
                long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest;
                Log.i("XXX", "progress.onERROR() callback.  "
                        + timeSinceSpeakCall + " millis since speak() was called.");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        progressView.setTextColor(Color.RED);
                        progressView.setText("PROGRESS: ERROR");
                    }
                });

            }
        };
        googleTTS.setOnUtteranceProgressListener(blurp);

    }

    // must happens AFTER tts is initialized
    private void assignFullSetOfVoicesToVoiceListView() {

        googleTTS.stop();

        List<Voice> tempVoiceList = new ArrayList<>();

        for (Voice v : googleTTS.getVoices()) {
            if (v.getLocale().getLanguage().contains("en")) { // only English voices
                tempVoiceList.add(v);
            }
        }

        // Sort the list alphabetically by name
        Collections.sort(tempVoiceList, new Comparator<Voice>() {
            @Override
            public int compare(Voice v1, Voice v2) {
                Log.i("XXX", "comparing item");
                return (v2.getName().compareToIgnoreCase(v1.getName()));
            }
        });

        VoiceAdapter tempAdapter = new VoiceAdapter(this, tempVoiceList);

        voiceListView.setAdapter(tempAdapter);
        swipeRefreshLayout.setRefreshing(false);
        progressView.setTextColor(Color.BLACK);
        progressView.setText("PROGRESS: ...");

    }

    private void speak() {
        HashMap<String, String> map = new HashMap<>();
        map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "merp");
        timeOfSpeakRequest = System.currentTimeMillis();
        googleTTS.speak(textToSpeak.getText().toString(), TextToSpeech.QUEUE_FLUSH, map);
    }

    // Checks if Google Engine is installed
    // ... (and gives more info in Logs).
    // The version number is going to dictate the quality of voices available
    private boolean isGoogleEngineInstalled() {

        final Intent ttsIntent = new Intent();
        ttsIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
        final PackageManager pm = getPackageManager();
        final List<ResolveInfo> list = pm.queryIntentActivities(ttsIntent, PackageManager.GET_META_DATA);

        boolean googleIsInstalled = false;

        for (int i = 0; i < list.size(); i++) {

            ResolveInfo resolveInfoUnderScrutiny = list.get(i);
            String engineName = resolveInfoUnderScrutiny.activityInfo.applicationInfo.packageName;

            if (engineName.equals("com.google.Android.tts")) {
                String version = "null";
                try {
                    version = pm.getPackageInfo(engineName,
                            PackageManager.GET_META_DATA).versionName;
                } catch (Exception e) {
                    Log.i("XXX", "Error getting google engine verion: " + e.toString());
                }
                Log.i("XXX", "Google engine version " + version + " is installed!");
                googleIsInstalled = true;
            } else {
                Log.i("XXX", "Google Engine is not installed!");
            }

        }
        return googleIsInstalled;
    }
}

VoiceAdapter.Java:

package [ your package name ];

import Android.content.Context;
import Android.graphics.Color;
import Android.speech.tts.Voice;
import Android.view.LayoutInflater;
import Android.view.View;
import Android.view.ViewGroup;
import Android.widget.BaseAdapter;
import Android.widget.TextView;

import Java.util.List;

public class VoiceAdapter extends BaseAdapter {

    private Context mContext;
    private LayoutInflater mInflater;
    private List<Voice> mDataSource;

    public VoiceAdapter(Context context, List<Voice> voicesToDisplay) {
        mContext = context;
        mDataSource = voicesToDisplay;
        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        return mDataSource.size();
    }

    @Override
    public Object getItem(int position) {
        return mDataSource.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // In a real app this method is not efficient,
        // and "View Holder Pattern" shoudl be used instead.
        View rowView = mInflater.inflate(R.layout.list_item_voice, parent, false);

        if (position%2 == 0) {
            rowView.setBackgroundColor(Color.rgb(245,245,245));
        }

        Voice voiceUnderScrutiny = mDataSource.get(position);

        // example output of Voice.toString() :
        // "Voice[Name: pt-br-x-afs#male_2-local, locale: pt_BR, quality: 400, latency: 200,
        // requiresNetwork: false, features: [networkTimeoutMs, notInstalled, networkRetriesCount]]"

        // Get title element
        TextView voiceTitleTextView =
                (TextView) rowView.findViewById(R.id.voice_title);

        TextView qualityTextView =
                (TextView) rowView.findViewById(R.id.voice_quality);

        TextView networkRequiredTextView =
                (TextView) rowView.findViewById(R.id.voice_network);

        TextView isInstalledTextView =
                (TextView) rowView.findViewById(R.id.voice_installed);

        TextView featuresTextView =
                (TextView) rowView.findViewById(R.id.voice_features);

        voiceTitleTextView.setText("VOICE NAME: " + voiceUnderScrutiny.getName());

        // Voice Quality...
        // ( https://developer.Android.com/reference/Android/speech/tts/Voice.html )
        // 100 = Very Low, 200 = Low, 300 = Normal, 400 = High, 500 = Very High
        qualityTextView.setText(  "QLTY: " + ((Integer) voiceUnderScrutiny.getQuality()).toString()  );
        if (voiceUnderScrutiny.getQuality() == 500) {
            qualityTextView.setTextColor(Color.GREEN); // set v. high quality to green
        }

        if (!voiceUnderScrutiny.isNetworkConnectionRequired()) {
            networkRequiredTextView.setText("NET_REQ?: NO");
        } else {
            networkRequiredTextView.setText("NET_REQ?: YES");
        }

        if (!voiceUnderScrutiny.getFeatures().contains("notInstalled")) {
            isInstalledTextView.setTextColor(Color.GREEN);
            isInstalledTextView.setText("INSTLLD?: YES");
        } else {
            isInstalledTextView.setTextColor(Color.RED);
            isInstalledTextView.setText("INSTLLD?: NO");
        }

        featuresTextView.setText("FEATURES: " + voiceUnderScrutiny.getFeatures().toString());

        return rowView;
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<Android.support.constraint.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:focusable="true"
    Android:focusableInTouchMode="true"
    tools:context=".MainActivity">

    <EditText
        Android:id="@+id/textToSpeak"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:ems="10"
        Android:inputType="textPersonName"
        Android:text="textToSpeak..."
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Android.support.v4.widget.SwipeRefreshLayout
        Android:id="@+id/swipeRefresh"
        Android:layout_width="0dp"
        Android:layout_height="0dp"
        Android:layout_marginBottom="8dp"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressView">

    <ListView
        Android:id="@+id/voiceListView"
        Android:layout_width="0dp"
        Android:layout_height="0dp"
        Android:layout_marginBottom="8dp"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp">

    </ListView>

    </Android.support.v4.widget.SwipeRefreshLayout>

    <TextView
        Android:id="@+id/progressView"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:text="UTTERANCE_PROGRESS:"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textToSpeak" />

</Android.support.constraint.ConstraintLayout>

list_item_voice.xml:

<?xml version="1.0" encoding="utf-8"?>
<Android.support.constraint.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"

    Android:layout_centerInParent="true"
    Android:paddingBottom="8dp"
    Android:paddingLeft="16dp"
    Android:paddingRight="16dp"
    Android:paddingTop="8dp"
    >

    <TextView
        Android:id="@+id/voice_title"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:text="NAME:"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <TextView
        Android:id="@+id/voice_installed"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginTop="8dp"
        Android:fontFamily="monospace"
        Android:text="INSTALLED? "
        Android:textAlignment="textStart"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/voice_network"
        app:layout_constraintTop_toBottomOf="@+id/voice_title" />

    <TextView
        Android:id="@+id/voice_quality"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:text="QUALITY:"
        app:layout_constraintEnd_toStartOf="@+id/voice_network"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/voice_title" />

    <TextView
        Android:id="@+id/voice_features"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginBottom="8dp"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:text="FEATURES:"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/voice_quality" />

    <TextView
        Android:id="@+id/voice_network"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_marginEnd="8dp"
        Android:layout_marginStart="8dp"
        Android:layout_marginTop="8dp"
        Android:text="NET_REQUIRED?"
        app:layout_constraintEnd_toStartOf="@+id/voice_installed"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/voice_quality"
        app:layout_constraintTop_toBottomOf="@+id/voice_title" />

</Android.support.constraint.ConstraintLayout>
1
Boober Bunz