零成本打造安卓AI提词器:Jetpack Compose与语音识别实战
2026/9/8 5:14:43 网站建设 项目流程

1. 项目背景与核心概念

在视频创作,尤其是口播、知识分享、直播等场景中,提词器是一个至关重要的工具。它能帮助创作者流畅地表达,避免忘词和卡顿,从而提升内容质量和录制效率。然而,专业的硬件提词器价格昂贵,动辄数千元,对于个人创作者或小型团队来说是一笔不小的开销。而软件提词器虽然选择多样,但要么功能单一,要么操作复杂,要么存在广告干扰,体验并不完美。

本项目旨在解决这个痛点:利用一部普通的安卓手机,零成本打造一个功能强大、体验流畅的私人专属提词器。我们不仅会实现基础的滚动字幕功能,还会融入AI能力,实现语音识别实时转字幕智能语速匹配等进阶特性,让提词过程更加智能和自然。整个项目将基于Android平台开发,使用Kotlin语言,结合Jetpack Compose构建现代化UI,并集成百度语音识别等AI服务,最终形成一个完整可安装的APK。通过这个实战项目,你将系统掌握Android应用开发、Compose UI、第三方SDK集成、多媒体处理等核心技能。

2. 环境准备与版本说明

在开始编码之前,请确保你的开发环境已就绪。以下是我开发时使用的环境,你可以根据实际情况进行微调。

操作系统:

  • Windows 10/11, macOS Monterey及以上,或 Ubuntu 20.04及以上均可。

集成开发环境 (IDE):

  • Android Studio: 推荐使用最新稳定版(如 Android Studio Flamingo | 2022.2.1 或更高)。它是官方支持的Android开发工具,对Compose和Kotlin的支持最好。

开发语言与框架:

  • Kotlin: 版本 1.8.0 或更高。Kotlin已是Android首推开发语言,其简洁语法非常适合本项目。
  • Android SDK: API Level 33 (Android 13) 作为targetSdkVersionminSdkVersion设置为 API 24 (Android 7.0),以覆盖绝大多数设备。
  • Jetpack Compose:BOM版本2023.06.01或更高。我们将使用Compose来构建所有用户界面,这是Android现代UI开发的未来。

第三方服务:

  • 百度AI开放平台-语音识别: 用于实现语音实时转文字功能。你需要前往百度AI开放平台注册账号并创建应用,以获取API KeySecret Key

项目依赖管理:

  • 使用Gradle作为构建系统,这是Android Studio的默认配置。

版本兼容性提示:本文的核心代码和配置思路具有通用性。如果未来Compose或相关库有重大更新,部分API可能需要调整,但整体架构和实现逻辑保持不变。重点在于理解每一步的原理。

3. 核心功能与原理拆解

我们的“手机提词器”核心功能可以拆解为以下几个模块,理解其原理是编码的基础。

3.1 文本滚动显示引擎

这是提词器的核心。原理是控制一个文本控件在屏幕上沿垂直方向匀速移动。

  • 关键参数
    • 滚动速度: 通常以“像素/秒”或“行/秒”来衡量,需要允许用户动态调节。
    • 字体与颜色: 为了清晰易读,需要支持大字体、高对比度颜色(如白字黑底或黑字白底)。
    • 暂停/继续: 必须能随时中断和恢复滚动,以适应录制时的节奏。
  • 实现思路: 在Compose中,我们可以通过Animatableanimate*AsState动画API来持续改变文本的垂直偏移量(offsetY),结合LaunchedEffect来驱动动画循环,从而实现平滑滚动。

3.2 语音识别集成

为了让提词器更智能,我们引入语音识别,实现“所说即所得”的提词稿生成。

  • 工作流程
    1. 权限申请: 需要获取RECORD_AUDIO录音权限。
    2. 音频采集: 使用Android的AudioRecord或更高阶的MediaRecorder捕获麦克风输入。
    3. 调用云端API: 将采集到的音频数据(通常需要转换为PCM格式)发送给百度语音识别REST API。
    4. 结果解析与显示: 接收API返回的JSON格式识别结果,提取出文字片段,并实时追加到提词文本框中。
  • 难点与优化
    • 网络延迟: 云端识别必然有延迟,UI上需要良好的加载状态提示。
    • 识别精度: 受环境噪音、口音影响,需要引导用户在安静环境下使用,并可能提供简单的文本编辑功能进行后期修正。

3.3 智能语速匹配(进阶)

基础滚动是匀速的,但人说话有快有慢。智能语速匹配旨在分析识别出的文本,动态微调滚动速度。

  • 简单策略
    • 根据标点符号(如句号、逗号)短暂减缓滚动速度,模拟自然停顿。
    • 检测到长难句时,稍微提升滚动速度以避免等待。
  • 实现考虑: 这是一个优化项,初期可以提供一个手动调节速度的滑块,后期再引入基于规则的简单智能调节。

3.4 用户界面与交互

一个直观易用的UI至关重要。

  • 核心界面
    • 主提词界面: 占据大部分屏幕的滚动文本区域,背景可调。
    • 控制面板: 悬浮或侧边栏,包含开始/暂停、速度调节、字体大小、颜色切换、语音识别开关等按钮。
    • 文本编辑界面: 用于初始粘贴或修改提词稿。

4. 完整实战:从零构建应用

让我们开始动手编码。我们将创建一个名为TelePrompter的新Android项目,选择Empty Activity模板,并确保语言为Kotlin,UI框架为Jetpack Compose

4.1 项目结构与依赖配置

首先,配置项目的build.gradle.kts文件。

1. 项目级build.gradle.kts(<project>/build.gradle.kts):主要配置仓库和插件版本。

plugins { id("com.android.application") version "8.1.0" apply false id("org.jetbrains.kotlin.android") version "1.8.0" apply false }

2. 模块级build.gradle.kts(<project>/app/build.gradle.kts):这是配置的核心,添加所有必要的依赖。

plugins { id("com.android.application") id("org.jetbrains.kotlin.android") } android { namespace = “com.yourcompany.teleprompter" compileSdk = 33 defaultConfig { applicationId = “com.yourcompany.teleprompter" minSdk = 24 targetSdk = 33 versionCode = 1 versionName = “1.0" testInstrumentationRunner = “androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile(“proguard-android-optimize.txt"), “proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = “1.8" } buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = “1.4.3" } packaging { resources { excludes += “/META-INF/{AL2.0,LGPL2.1}" } } } dependencies { // Android 核心库 implementation(“androidx.core:core-ktx:1.10.1") implementation(“androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation(“androidx.activity:activity-compose:1.7.2") implementation(platform(“androidx.compose:compose-bom:2023.06.01")) implementation(“androidx.compose.ui:ui") implementation(“androidx.compose.ui:ui-graphics") implementation(“androidx.compose.ui:ui-tooling-preview") implementation(“androidx.compose.material3:material3") testImplementation(“junit:junit:4.13.2") androidTestImplementation(“androidx.test.ext:junit:1.1.5") androidTestImplementation(“androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform(“androidx.compose:compose-bom:2023.06.01")) androidTestImplementation(“androidx.compose.ui:ui-test-junit4") debugImplementation(“androidx.compose.ui:ui-tooling") debugImplementation(“androidx.compose.ui:ui-test-manifest") // 视图模型,用于管理UI状态 implementation(“androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") // 网络请求 (用于调用百度语音识别API) implementation(“com.squareup.retrofit2:retrofit:2.9.0") implementation(“com.squareup.retrofit2:converter-gson:2.9.0") implementation(“com.squareup.okhttp3:logging-interceptor:4.11.0") // 权限请求 implementation(“com.google.accompanist:accompanist-permissions:0.31.5-beta") }

注意:com.google.accompanist:accompanist-permissions库用于简化Compose中的权限请求,但请注意其可能已迁移至Android官方库,请根据你使用的Compose版本查阅最新文档。

3. 百度语音识别依赖:百度官方通常提供aar包或Maven依赖。假设我们使用网络请求的方式, Retrofit依赖已添加。你需要将百度语音识别SDK(如果有)的aar文件放入app/libs/目录,并在build.gradle.ktsdependencies块中添加:

implementation(fileTree(mapOf(“dir” to “libs”, “include” to listOf(“*.jar”, “*.aar”))))

或者,如果百度提供了Maven仓库,则添加对应的implementation语句。

4.2 实现基础提词滚动界面

我们首先构建最核心的滚动文本显示功能。

1. 定义数据状态 (ui/TeleprompterState.kt):使用ViewModelStateFlow/MutableStateFlow来管理应用状态,这是Compose推荐的做法。

import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class TeleprompterViewModel : ViewModel() { // 提词文本内容 private val _scriptText = MutableStateFlow(“欢迎使用手机提词器!请在此输入或粘贴您的讲稿。\n第二行测试文本。”) val scriptText: StateFlow<String> = _scriptText.asStateFlow() // 滚动位置(像素偏移量) private val _scrollOffset = MutableStateFlow(0f) val scrollOffset: StateFlow<Float> = _scrollOffset.asStateFlow() // 滚动速度(像素/秒) private val _scrollSpeed = MutableStateFlow(60f) val scrollSpeed: StateFlow<Float> = _scrollSpeed.asStateFlow() // 是否正在滚动 private val _isScrolling = MutableStateFlow(false) val isScrolling: StateFlow<Boolean> = _isScrolling.asStateFlow() // 字体大小(SP) private val _fontSize = MutableStateFlow(24f) val fontSize: StateFlow<Float> = _fontSize.asStateFlow() // 文本颜色 private val _textColor = MutableStateFlow(androidx.compose.ui.graphics.Color.White) val textColor: StateFlow<androidx.compose.ui.graphics.Color> = _textColor.asStateFlow() // 背景颜色 private val _backgroundColor = MutableStateFlow(androidx.compose.ui.graphics.Color.Black) val backgroundColor: StateFlow<androidx.compose.ui.graphics.Color> = _backgroundColor.asStateFlow() fun updateScript(newText: String) { _scriptText.value = newText } fun setScrollSpeed(speed: Float) { _scrollSpeed.value = speed } fun setFontSize(size: Float) { _fontSize.value = size } fun toggleScrolling() { _isScrolling.value = !_isScrolling.value if (_isScrolling.value) { startAutoScroll() } } fun resetScroll() { _scrollOffset.value = 0f _isScrolling.value = false } private fun startAutoScroll() { viewModelScope.launch { while (isScrolling.value) { // 根据速度和时间增量更新偏移量 // 这是一个简化的逻辑,实际应该基于时间差计算 _scrollOffset.value += _scrollSpeed.value / 60 // 假设60FPS kotlinx.coroutines.delay(16) // 约60FPS的延迟 } } } }

2. 创建主提词界面 (ui/TeleprompterScreen.kt):

import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun TeleprompterScreen(viewModel: TeleprompterViewModel = viewModel()) { val script by viewModel.scriptText.collectAsState() val scrollOffset by viewModel.scrollOffset.collectAsState() val scrollSpeed by viewModel.scrollSpeed.collectAsState() val isScrolling by viewModel.isScrolling.collectAsState() val fontSize by viewModel.fontSize.collectAsState() val textColor by viewModel.textColor.collectAsState() val backgroundColor by viewModel.backgroundColor.collectAsState() // 使用动画使滚动更平滑(可选) val animatedOffset by animateFloatAsState( targetValue = scrollOffset, animationSpec = if (isScrolling) tween(durationMillis = 16) else snap() ) Box( modifier = Modifier .fillMaxSize() .background(backgroundColor) .clipToBounds() // 确保文本超出部分被裁剪 ) { // 滚动的文本内容 Text( text = script, color = textColor, fontSize = fontSize.sp, fontWeight = FontWeight.Normal, modifier = Modifier .fillMaxWidth() .align(Alignment.TopCenter) .padding(horizontal = 32.dp) .offset(y = animatedOffset.dp) // 应用垂直偏移实现滚动 ) // 控制面板 (可以设计为悬浮按钮或底部栏) ControlPanel( isScrolling = isScrolling, scrollSpeed = scrollSpeed, onSpeedChange = { viewModel.setScrollSpeed(it) }, onToggleScroll = { viewModel.toggleScrolling() }, onReset = { viewModel.resetScroll() }, modifier = Modifier.align(Alignment.BottomCenter) ) } } @Composable fun ControlPanel( isScrolling: Boolean, scrollSpeed: Float, onSpeedChange: (Float) -> Unit, onToggleScroll: () -> Unit, onReset: () -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier .fillMaxWidth() .background(Color.DarkGray.copy(alpha = 0.8f)) .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text(“滚动速度: ${“%.1f”.format(scrollSpeed)}“, color = Color.White) Slider( value = scrollSpeed, onValueChange = onSpeedChange, valueRange = 20f..200f, modifier = Modifier.fillMaxWidth() ) Button(onClick = onToggleScroll) { Text(if (isScrolling) “暂停” else “开始”) } Button(onClick = onReset, modifier = Modifier.padding(top = 8.dp)) { Text(“重置到顶部”) } } }

3. 在MainActivity中调用 (MainActivity.kt):

import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import com.yourcompany.teleprompter.ui.TeleprompterScreen import com.yourcompany.teleprompter.ui.theme.TeleprompterTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { TeleprompterTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { TeleprompterScreen() } } } } }

现在运行应用,你应该能看到一个黑色背景、白色文字的界面,点击“开始”按钮,文本会向上滚动。速度可以通过滑块调节。

4.3 集成语音识别功能

这是一个进阶功能。由于涉及网络权限和异步操作,代码会复杂一些。这里给出核心步骤和代码框架。

1. 权限处理 (utils/PermissionUtils.kt或直接在ViewModel中):使用accompanist-permissions或Android原生API请求录音权限。

// 在 ViewModel 或一个专门类中管理权限状态 @Composable fun RequestRecordAudioPermission() { val permissionState = rememberPermissionState(android.Manifest.permission.RECORD_AUDIO) if (!permissionState.status.isGranted) { LaunchedEffect(permissionState) { permissionState.launchPermissionRequest() } } // 根据 permissionState.status 更新UI或逻辑 }

2. 百度语音识别服务封装 (service/BaiduAsrService.kt):这是一个简化版的Retrofit服务接口和调用类。

import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.Body import retrofit2.http.Header import retrofit2.http.POST import java.util.concurrent.TimeUnit // 百度语音识别API返回的数据结构(根据官方文档定义) data class AsrResponse( val result: List<String>?, val err_no: Int, val err_msg: String, val sn: String, val corpus_no: String? ) // 请求体结构 data class AsrRequest( val format: String = “pcm”, val rate: Int = 16000, val channel: Int = 1, val cuid: String, val token: String, val speech: String, // Base64编码的音频数据 val len: Int // 音频数据长度 ) interface BaiduAsrApi { @POST(“/v2/asr") suspend fun recognize( @Header(“Content-Type”) contentType: String = “application/json”, @Body request: AsrRequest ): AsrResponse } object BaiduAsrService { private const val BASE_URL = “https://vop.baidu.com" private val client = OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() val api: BaiduAsrApi = retrofit.create(BaiduAsrApi::class.java) // 获取Access Token (需要你的 API Key 和 Secret Key) suspend fun getAccessToken(apiKey: String, secretKey: String): String? { // 实现获取Token的逻辑,通常需要另一个网络请求 // 返回获取到的 token,或 null return null } }

3. 在ViewModel中集成语音识别逻辑:扩展TeleprompterViewModel,添加音频录制、识别和文本追加功能。这部分涉及AudioRecord的使用和协程管理,代码较长,核心思路是:

  • toggleVoiceRecognition函数中,开始或停止录音。
  • 录音时,将音频数据放入缓冲区,定期(如每1秒)发送到BaiduAsrService进行识别。
  • 收到识别结果后,将文本追加到_scriptText.value的末尾。

4.4 完善功能与UI

  • 文本编辑界面: 创建一个新的Composable屏幕,使用TextFieldBasicTextField来编辑scriptText
  • 设置界面: 允许用户修改字体大小、颜色主题(如白底黑字、黑底黄字等)。
  • 本地存储: 使用DataStoreSharedPreferences保存用户设置和最近的文稿。
  • 导出功能: 可以将最终的文稿(含语音识别添加的内容)保存为文本文件。

5. 常见问题与排查思路

在开发和测试过程中,你可能会遇到以下问题:

问题现象可能原因排查与解决思路
应用安装失败设备不满足minSdkVersion要求;签名冲突。检查设备Android版本;在Android Studio中执行Build -> Clean ProjectRebuild
文本不滚动或滚动卡顿ViewModel中的滚动逻辑有误;动画刷新率太低。检查startAutoScroll协程是否正常启动;确保scrollOffset的更新能触发UI重组;考虑使用withFrameMillis进行更精确的基于时间的动画。
语音识别无反应未授予录音权限;网络问题;百度API配置错误。1. 检查应用权限设置。2. 检查网络连接。3. 确认百度AI应用的API KeySecret Key正确,且服务“语音识别”已开通。4. 查看Logcat中的网络请求和错误日志。
识别结果不准或延迟高环境噪音大;音频格式或参数不匹配。1. 确保在安静环境下测试。2. 检查AsrRequest中的formatratechannel是否与录音参数严格一致。3. 考虑使用VAD(语音活动检测)来只在说话时发送音频,减少无效请求。
UI布局错乱或预览不显示Compose版本不兼容;预览函数参数问题。1. 同步Gradle依赖。2. 确保@Preview注解的函数提供了默认参数或使用@PreviewParameter。3. 尝试File -> Invalidate Caches and Restart
控制面板遮挡文本布局层级或对齐方式问题。调整ControlPanelModifier.align位置或使用BoxWithConstraints动态计算布局。

6. 最佳实践与工程建议

将一个小想法做成一个健壮的应用,需要关注更多工程细节。

  1. 状态管理规范化

    • 始终坚持使用ViewModel+StateFlow/MutableStateFlow(或LiveData)来管理UI状态。避免在@Composable函数中直接持有可变状态。
    • 对于复杂状态,考虑使用sealed class来定义状态,使状态变化更清晰。
    • 示例:
    sealed interface RecognitionState { object Idle : RecognitionState object Recording : RecognitionState data class Recognizing(val progress: Int) : RecognitionState data class Error(val message: String) : RecognitionState data class Result(val text: String) : RecognitionState }
  2. 网络请求与错误处理

    • 将所有网络操作(如语音识别)放在ViewModelRepository层,使用协程的try-catch进行包装。
    • 在UI层收集状态,并根据不同的状态(加载中、成功、失败)显示相应的界面(如加载框、错误提示Snackbar)。
    • 合理设置超时时间,并考虑重试机制。
  3. 性能优化

    • 滚动性能: 确保滚动文本的TextComposable 不会在每一帧都进行昂贵的重组。如果文稿极长,考虑使用LazyColumn或其他虚拟化列表,但滚动逻辑会变得更复杂。对于本项目,分段渲染文本可能是一个优化方向。
    • 内存管理: 语音识别时,音频数据缓冲区要及时清理,避免内存泄漏。在ViewModelonCleared方法中取消所有协程。
  4. 用户体验优化

    • 手势支持: 为提词界面添加双击暂停、上下滑动微调滚动位置等功能。
    • 多种颜色主题: 提供几套预设的颜色方案(如白底黑字、黑底绿字、蓝底黄字),适应不同拍摄环境。
    • 倒计时功能: 在开始滚动前提供3秒倒计时,让创作者做好准备。
    • 镜像模式: 对于前置摄像头拍摄,提供文本水平镜像翻转的选项。
  5. 代码结构与可维护性

    • 遵循单一职责原则,将语音识别、文件存储、设置管理等逻辑抽离到独立的RepositoryUseCase类中。
    • 使用依赖注入(如Hilt)来管理ViewModelRepositoryService的实例,使代码更可测试。
    • 为关键业务逻辑编写单元测试。
  6. 发布前检查

    • 隐私政策: 因为应用需要录音权限,必须在应用内提供隐私政策链接,说明音频数据的使用方式(仅用于本地识别或上传至百度服务器)。
    • 混淆配置: 在proguard-rules.pro中添加Retrofit、Gson等库的混淆保留规则。
    • 图标与名称: 设计一个专业的应用图标和名称。

通过这个项目,你不仅得到了一个零成本的强大提词器,更完成了一次完整的Android现代应用开发实战。从UI构建、状态管理、第三方SDK集成,到性能调优和用户体验打磨,每一个环节都蕴含着宝贵的工程经验。你可以在此基础上继续扩展,比如加入AI文稿润色、多语言翻译提词、与云存储同步文稿等功能,让它真正成为一个独一无二的创作利器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询