[MT8167S][Android 9.0]MTK平台的LCM流程分析——lk层

前言

在MTK平台点亮一块屏不是难事,因为MTK的LCM框架很完善,我们的屏驱动需要的工作不是很多。不过要想成为一个优秀的工程师,不能仅仅满足于此,至少要了解整个框架流程。

MTK平台的lcm流程分为Lk和kernel两个阶段,这篇文章我们先来分析lk阶段的流程。

正文

lk阶段起来后,display相关的初始化主要在platform_init()函数中完成。

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\platform.c

void disp_thread_routine()
{
#ifdef LK_PROFILING
	unsigned int time_disp_init;
	time_disp_init = get_timer(0);
#endif
	/* initialize the frame buffet information */
	g_fb_size = mt_disp_get_vram_size(); ---------------------1/* framebuffer的起始地址 */
	g_fb_base = mblock_reserve(&g_boot_arg->mblock_info, g_fb_size, 0x10000, 0x100000000, RANKMAX);

	dprintf(CRITICAL, "FB base = 0x%x, FB size = %d\n", g_fb_base, g_fb_size);

#ifdef VDEC_LDVT_RESERVED_MEMORY_SIZE
	g_vdec_base = mblock_reserve(&g_boot_arg->mblock_info, VDEC_LDVT_RESERVED_MEMORY_SIZE, 0x200000, 0x100000000, RANKMAX);
	g_vdec_base = ALIGN_TO(g_vdec_base,0x200000);

	dprintf(CRITICAL, "VDEC base = 0x%x, VDEC size = %d\n", g_vdec_base, 0x10000000);
#endif

	mt_disp_init((void *)g_fb_base); --------------------2/* show black picture fisrtly in case of  backlight is on before nothing is drawed*/
	mt_disp_fill_rect(0, 0, CFG_DISPLAY_WIDTH, CFG_DISPLAY_HEIGHT, 0x0); --------------------3mt_disp_update(0, 0, CFG_DISPLAY_WIDTH, CFG_DISPLAY_HEIGHT); //将framebuffer的数据更新到LCM
#ifdef LK_PROFILING
	dprintf(INFO, "[PROFILE] ------- disp init takes %d ms -------- \n", (int)get_timer(time_disp_init));
#endif

	drv_video_init();

	 EVENT
	event_signal(&disp_ready_event, true);
}

void platform_init(void)
{
    ...
     THREAD CREATE
	thread_resume(thread_create("disp", &disp_thread_routine, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
    ...
}

(1)

首先是获取framebuffer的大小:

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\primary_display.c

UINT32 DISP_GetFBRamSize(void)
{
	return ALIGN_TO(DISP_GetScreenWidth(), MTK_FB_ALIGNMENT) * DISP_GetScreenHeight() * ((DISP_GetScreenBpp() + 7) >> 3) * DISP_GetPages();
}

unsigned int DISP_GetVRamSize(void)
{
	static UINT32 vramSize = 0;

	if (0 == vramSize) {
		vramSize = DISP_GetFBRamSize();

		vramSize += DAL_GetLayerSize();

#if 1
		if (g_is_64bit_kernel) {
			vramSize = ALIGN_TO(vramSize, 0x400000);  // both start addr and size needs 2MB align, so size must 4MB align
		} else {
			vramSize = ALIGN_TO(vramSize, 0x100000);  // both start addr and size needs 2MB align, so size must 4MB align
		}
#else
		vramSize = ALIGN_TO(vramSize, 0x10000);  // just align to 64KB is OK
#endif

		DISPMSG("^^ DISP_GetVRamSize: %u bytes\n", vramSize);
	}

	return vramSize;
}

先调用DISP_GetFBRamSize()去计算整个buffer的大小,这就涉及到screen的width、height和bpp的值了。我们先看一下怎么获取width值:

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\primary_display.c

int primary_display_get_width(void)
{
	if (pgc->plcm == NULL) {
		pgc->plcm = disp_lcm_probe(NULL, LCM_INTERFACE_NOTDEFINED);
		DISPMSG("lcm handle is null, after probe:0x%08x\n",pgc->plcm);
		if (pgc->plcm == NULL)
			return 0;
	}

	if (pgc->plcm->params) {
		return pgc->plcm->params->width;
	} else {
		DISPERR("lcm_params is null!\n");
		return 0;
	}
}

UINT32 DISP_GetScreenWidth(void)
{
	return primary_display_get_width();
}

由于第一次注册,所以pgc->plcm是NULL,就先调用disp_lcm_probe()注册我们的lcm模块:

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\disp_lcm.c

int _lcm_count(void)
{
	return lcm_count;
}

disp_lcm_handle *disp_lcm_probe(char *plcm_name, LCM_INTERFACE_ID lcm_id)
{
	DISPFUNC();

	int ret = 0;
	bool isLCMFound = false;
	bool isLCMConnected = false;
	bool isDriverIDFound = false;

	LCM_DRIVER *lcm_drv = NULL;
	LCM_PARAMS *lcm_param = NULL;
	disp_lcm_handle *plcm = NULL;


	if (_lcm_count() == 0) {
		DISPERR("no lcm driver defined in linux kernel driver\n");
		return NULL;
	} else if (_lcm_count() == 1) {  ------------------------1.1)
		lcm_drv = lcm_driver_list[0];

		isLCMFound = true;
	} else {
		...
	}

	if (isLCMFound == false) {
		DISPERR("FATAL ERROR!!!No LCM Driver defined\n");
		return NULL;
	}

	plcm = &_disp_lcm_driver[0];
	lcm_param = &_disp_lcm_params;
	if (plcm && lcm_param) {
		plcm->params = lcm_param;
		plcm->drv = lcm_drv; //指向我们自定义的LCM_DRIVER
	} else {
		DISPERR("FATAL ERROR!!!kzalloc plcm and plcm->params failed\n");
		goto FAIL;
	}

#if defined(MTK_LCM_DEVICE_TREE_SUPPORT)
	load_lcm_resources_from_DT(plcm->drv);
#endif

	plcm->drv->get_params(plcm->params); --------------------------1.2)
	plcm->lcm_if_id = plcm->params->lcm_if; //get_params中没有设置
	/* below code is for lcm driver forward compatible */
	if (plcm->params->type == LCM_TYPE_DSI && plcm->params->lcm_if == LCM_INTERFACE_NOTDEFINED)
		plcm->lcm_if_id = LCM_INTERFACE_DSI0; //get_params函数中可以看到设置为了DSI类型
	if (plcm->params->type == LCM_TYPE_DPI && plcm->params->lcm_if == LCM_INTERFACE_NOTDEFINED)
		plcm->lcm_if_id = LCM_INTERFACE_DPI0;
	if (plcm->params->type == LCM_TYPE_DBI && plcm->params->lcm_if == LCM_INTERFACE_NOTDEFINED)
		plcm->lcm_if_id = LCM_INTERFACE_DBI0;
#if 1
	if (LCM_TYPE_DSI == plcm->params->type) {
		disp_path_handle handle = NULL;
		int ret = 0;
		char buffer = 0;
		handle = _display_interface_path_init(plcm); //暂时不分析
#if 0
		...
#endif
		_display_interface_path_deinit(handle);
	}

	plcm->is_connected = isLCMConnected;
#endif

	_dump_lcm_info(plcm);
	return plcm;

FAIL:

	return NULL;
}

(1.1)

首先就是获取你注册的lcm驱动的数量,正常都是只注册一个,也就是你移植的屏驱动:

vendor\mediatek\proprietary\bootable\bootloader\lk\dev\lcm\mt65xx_lcm_list.c

extern LCM_DRIVER hn116wx1_100_dsi_edp_A15_A17_lcm_drv;

LCM_DRIVER *lcm_driver_list[] = {
#if defined(HN116WX1_100_DSI_EDP_A15_A17)
		&hn116wx1_100_dsi_edp_A15_A17_lcm_drv,
#endif
    ...
}

unsigned int lcm_count = sizeof(lcm_driver_list) / sizeof(LCM_DRIVER *);

在调试一款新的屏,MTK平台是有特定的套路的,比如这里就要我们自己的屏驱动LCM_DRIVER填充到lcm_driver_list列表中。而HN116WX1_100_DSI_EDP_A15_A17是在device/mediatek/mt8167/ProjectConfig.mk中的CUSTOM_LK_LCM和CUSTOM_UBOOT_LCM定义的。现在看一下我们实现的LCM_DRIVER hn116wx1_100_dsi_edp_A15_A17_lcm_drv。

首先我们需要在vendor/mediatek/proprietary/bootable/bootloader/lk/dev/lcm/目录新建我们驱动的目录,比如我的代码目录HN116WX1_100_DSI_EDP_A15_A17,这个要和CUSTOM_LK_LCM定义的一致,否则编译不到我们的驱动。

vendor\mediatek\proprietary\bootable\bootloader\lk\dev\lcm\HN116WX1_100_DSI_EDP_A15_A17\HN116WX1_100_DSI_EDP_A15_A17.c

LCM_DRIVER hn116wx1_100_dsi_edp_A15_A17_lcm_drv = {
    .name           = "hn116wx1_100_dsi_edp_A15_A17",
    .set_util_funcs = lcm_set_util_funcs,
    .get_params     = lcm_get_params,
    .init           = lcm_init,
    .init_power     = lcm_init_power,
    .suspend        = lcm_suspend,
    .resume         = lcm_resume,
    //.esd_check      = IT6151_ESD_Check,
    //.esd_recover    = IT6151_ESD_Recover,
};

我们需要填充LCM_DRIVER结构体的成员,主要包括初始化、参数获取、睡眠和唤醒等函数。函数的具体作用我们后面再讲。

(1.2)

这一步就是调用我们屏驱动的.get_params函数,并赋值给变量_disp_lcm_params

vendor\mediatek\proprietary\bootable\bootloader\lk\dev\lcm\HN116WX1_100_DSI_EDP_A15_A17\HN116WX1_100_DSI_EDP_A15_A17.c

static void lcm_get_params(LCM_PARAMS *params)
{
    memset(params, 0, sizeof(LCM_PARAMS));

    params->type   = LCM_TYPE_DSI;

    params->width  = FRAME_WIDTH;
    params->height = FRAME_HEIGHT;

#if (LCM_DSI_CMD_MODE)
    params->dsi.mode   = CMD_MODE;
#else
    params->dsi.mode   = SYNC_PULSE_VDO_MODE;//SYNC_PULSE_VDO_MODE; //BURST_VDO_MODE; //SYNC_EVENT_VDO_MODE;
#endif
    params->dsi.LANE_NUM                = LCM_FOUR_LANE;
    params->dsi.data_format.format      = LCM_DSI_FORMAT_RGB888;//LCM_DSI_FORMAT_RGB888;

    params->dsi.PS = LCM_PACKED_PS_24BIT_RGB888; //LCM_PACKED_PS_24BIT_RGB888;//LCM_LOOSELY_PS_18BIT_RGB666;//LCM_PACKED_PS_18BIT_RGB666;

    params->dsi.vertical_sync_active                = 6; //4; //6; //(12-4-4); //1;
    params->dsi.vertical_backporch                  = 3;//8; //6; //4; //6; //10;
    params->dsi.vertical_frontporch                 = 3;//8;//6; //4//6; //10;
    params->dsi.vertical_active_line                = FRAME_HEIGHT;

    params->dsi.horizontal_sync_active              = 32;//4; //40; //44; //(120-40-40); //1;
    params->dsi.horizontal_backporch                = 65;//60;//43;//44; //40; //44; //57;
    params->dsi.horizontal_frontporch               = 65;//60;//45;//108;//44; //40; //44; //32;
    params->dsi.horizontal_active_pixel             = FRAME_WIDTH;

    params->dsi.PLL_CLOCK = 440;//180;//216;//240;//LCM_DSI_6589_PLL_CLOCK_NULL; //LCM_DSI_6589_PLL_CLOCK_396_5;

    params->dsi.cont_clock = 1;

    params->dsi.ssc_disable = 1;
    params->dsi.edp_panel = 1;
}

这里的参数都非常重要,设置不对就会影响屏幕点亮,我们分别了解一下各参数的意义。

(a)LCM_TYPE_DSI

MIPI标准中有DBI、DPI和DSI三种display接口,我们平台的显示屏是DSI接口的

(b)FRAME_WIDTH和FRAME_HEIGHT

显示屏的分辨率参数,比如我们平台的是1920*1080

#define FRAME_WIDTH  (1920) //(800)
#define FRAME_HEIGHT (1080) //(1280)

(c)SYNC_PULSE_VDO_MODE

首先介绍一下,DSI的两种基本的操作模式:命令模式(Command Mode)和视频模式(Video Mode)。顾名思义,命令模式主要是用来进行命令操作的,如应用处理器向显示设备(如LCD)发送相关命令,显示设备返回对应的相关数据。显然,命令模式需要双向的数据通道,即需要Data Lane支持Bidirectional模式。视频模式则是用来传输用于显示的视频或图像数据的,且视频信息只能在HS模式下进行传输。

因此,我们这里的dsi.mode设置为视频模式,而视频模式又分三种子模式:

  • Non-Busrt Mode with Sync Pluses

  • Non-Burst Mode with Sync Events

  • Burst Mode

下图是这三种模式的时序图:
在这里插入图片描述

Non-Busrt Mode with Sync Pluses模式主要是用于发送DPI(Display Pixel Interface)类型的数据,可以精确地匹配DPI的像素传输速率,以及时序事件的宽度(Widths of timing events),如同步脉冲。因此,Non-Busrt Mode with Sync Pluses模式的每一个Sync Start都必须都要有一个唯一的Sync End与之对应,比如VS都对应一个VSE,HSS都对应一个HSE。

Non-Busrt Mode with Sync Events模式与Non-Busrt Mode with Sync Pluses模式类似,可以认为前者是后者的简化版本。因为Non-Busrt Mode with Sync Events模式不需要精确控制时序事件的宽度,所以Non-Busrt Mode with Sync Events只有Sync Start,而没有Sync End,所以只有VS和和HSS,没有对应的VSE和HSE。

Burst Mode相比于Non-Burst Mode with Sync Events模式,可以更快完成一帧(或一行)图像像素的传输,因此可以节省更多的时间以进入LP模式,进而降低系统的功耗。但是,需要注意的是,Burst Mode需要使用的是经过时序压缩的数据格式(Time-compressed burst format),所以上图中Burst Mode的HACT部分会比Non-Burst Mode with Sync Events的短。
在这里插入图片描述

再回到我们自己的平台,一般情况我们的panel IC的datasheet会注明支持哪种模式:
在这里插入图片描述

从上面的datasheet可以看出来,我这个panel IC同时支持3种video mode,这里我们通过宏SYNC_PULSE_VDO_MODE,将video mode设置为Non-Busrt Mode with Sync Pluses。

(d)LCM_DSI_FORMAT_RGB888和LCM_PACKED_PS_24BIT_RGB888

这个主要看panel IC支持什么格式,设置对应的格式就好。

(e)“vertical_"和"horizontal_

    params->dsi.vertical_sync_active                = 6; //4; //6; //(12-4-4); //1;
    params->dsi.vertical_backporch                  = 3;//8; //6; //4; //6; //10;
    params->dsi.vertical_frontporch                 = 3;//8;//6; //4//6; //10;
    params->dsi.vertical_active_line                = FRAME_HEIGHT;

    params->dsi.horizontal_sync_active              = 32;//4; //40; //44; //(120-40-40); //1;
    params->dsi.horizontal_backporch                = 65;//60;//43;//44; //40; //44; //57;
    params->dsi.horizontal_frontporch               = 65;//60;//45;//108;//44; //40; //44; //32;
    params->dsi.horizontal_active_pixel             = FRAME_WIDTH;

这几个参数非常重要,不过我们可以很容易的向panel厂家获得。

(f)dsi.PLL_CLOCK

这个频率可以根据(e)提到的前后肩参数算出来,可以参考这篇文章:

disp_lcm_probe()函数走完后,我们也就能获得screen的width值。我们再回到DISP_GetFBRamSize()函数:

UINT32 DISP_GetFBRamSize(void)
{
	return ALIGN_TO(DISP_GetScreenWidth(), MTK_FB_ALIGNMENT) * DISP_GetScreenHeight() * ((DISP_GetScreenBpp() + 7) >> 3) * DISP_GetPages();
}

同理,DISP_GetScreenHeight()和获取width值一样,都是在lcm_get_params()里面设置的。DISP_GetScreenBpp()设置了一个固定的BPP值为32,DISP_GetPages()的含义不是很清楚,不过从注释来看是设置了需要内存两倍大小的framebuffer?

static UINT32 disp_fb_bpp = 32;     ///ARGB8888
static UINT32 disp_fb_pages = 3;     ///double buffer
UINT32 DISP_GetScreenBpp(void)
{
	return disp_fb_bpp;
}

UINT32 DISP_GetPages(void)
{
	return disp_fb_pages;   // Double Buffers
}

(2)

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\mt_disp_drv.c

void mt_disp_init(void *lcdbase)
{
	...
    primary_display_init(NULL);
    ...
}

mt_disp_init()顾名思义就是初始化显示相关的东西

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\primary_display.c

int primary_display_init(char *lcm_name)
{
	...
    ret = disp_lcm_init(pgc->plcm);
    ...
}

disp_lcm_init()函数就是调用我们自己LCM驱动LCM_DRIVER中定义的函数

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\disp_lcm.c

int disp_lcm_init(disp_lcm_handle *plcm)
{
	DISPFUNC();
	LCM_DRIVER *lcm_drv = NULL;
	bool isLCMConnected = false;

	if (_is_lcm_inited(plcm)) {
		lcm_drv = plcm->drv;

		if (lcm_drv->init_power) {
			lcm_drv->init_power();
		}

		if (lcm_drv->init) {
			if (!disp_lcm_is_inited(plcm)) {
				lcm_drv->init();
			}
		} else {
			DISPERR("FATAL ERROR, lcm_drv->init is null\n");
			return -1;
		}
        
		...

		return 0;
	} else {
		DISPERR("plcm is null\n");
		return -1;
	}
}

再回忆一下我们的LCM驱动:

LCM_DRIVER hn116wx1_100_dsi_edp_A15_A17_lcm_drv = {
    .name           = "hn116wx1_100_dsi_edp_A15_A17",
    .set_util_funcs = lcm_set_util_funcs,
    .get_params     = lcm_get_params,
    .init           = lcm_init,
    .init_power     = lcm_init_power,
    .suspend        = lcm_suspend,
    .resume         = lcm_resume,
    //.esd_check      = IT6151_ESD_Check,
    //.esd_recover    = IT6151_ESD_Recover,
};

所以lcm_drv->init_power()其实就是调用的lcm_init_power,不过我们这个函数为空,没有实际动作。然后就是lcm_drv->init()初始化LCM,一般就是给LCM下发init code:

static struct LCM_setting_table lcm_initialization_setting[] = {
 
       {0xB0, 1, {0x01}}, //含义:命令, 参数个数, 参数
       ...
       {0xE2, 1, {0x07}},

       {0x11, 0, {} }, //DCS_Short_Write_NP(0x11) 不带参数的命令
       {REGFLAG_DELAY, 120, {} }, //Delay(120),延时120微秒
       {0x29, 0, {} }, //DCS_Short_Write_NP(0x29)
       {REGFLAG_DELAY, 20, {} },
       {REGFLAG_END_OF_TABLE, 0x00, {} } //表示结束,都需要添加
};

static void init_lcm_registers(void)
{
	push_table(lcm_initialization_setting,
		    sizeof(lcm_initialization_setting) / sizeof(struct LCM_setting_table), 1);
}

static void lcm_init_lcm(void)
{
#ifdef BUILD_LK
	cmdline_append("lcm_name=hx82790d");
	init_lcm_registers();
#endif
}

上面的初始化代码是MTK平台很常见的,而struct LCM_setting_table lcm_initialization_setting就是我们需要下发给LCM的初始化参数,这个都是屏厂提供(我大概注释了一下屏参的格式含义)。

(3)

初始化完LCM后,在Lk阶段就可以显示logo了。

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\mt_logo.c

/*
 * Fill rectangle region for with black  or other color  
 *
 */
void mt_disp_fill_rect(UINT32 left, UINT32 top,
                           UINT32 right, UINT32 bottom,
                           UINT32 color)
{
    dprintf(0, "[lk logo: %s %d]\n",__FUNCTION__,__LINE__);   
    init_fb_screen();
    RECT_REGION_T rect = {left, top, right, bottom};
    
    fill_rect_with_color(mt_get_fb_addr(), rect, color, phical_screen);     
}

从注释来看也知道,这是在初始化阶段先给framebuffer填全0数据,使屏幕显示黑色。另外我们也关注一下init_fb_screen()里面的初始化动作:

void init_fb_screen()
{
    dprintf(0, "[lk logo: %s %d]\n",__FUNCTION__,__LINE__);
    ...

    if(0 == strncmp(MTK_LCM_PHYSICAL_ROTATION, "270", 3))
    { 
        phical_screen.rotation = 270;
    } else if(0 == strncmp(MTK_LCM_PHYSICAL_ROTATION, "90", 2)){ 
        phical_screen.rotation = 90;
    } else if(0 == strncmp(MTK_LCM_PHYSICAL_ROTATION, "180", 3) && (phical_screen.need180Adjust == 1)){ 
        phical_screen.rotation = 180;   
    } else {
        phical_screen.rotation = 0;   
    }
    
    ...
    
}

我们重点关注一下MTK_LCM_PHYSICAL_ROTATION,这是定义屏幕画面旋转角度的,比如我们平板是竖屏横用可能就需要旋转90度,可以在device/mediatek/mt8167/ProjectConfig.mk中定义MTK_LCM_PHYSICAL_ROTATION的值。

通过漫长的初始化之后,我们准备开始显示logo画面,还是在platform_init()阶段

vendor\mediatek\proprietary\bootable\bootloader\lk\platform\mt8167\mt_logo.c

/*
 * Show first boot logo when phone boot up
 *
 */
void mt_disp_show_boot_logo(void)
{
    dprintf(0, "[lk logo: %s %d]\n",__FUNCTION__,__LINE__);    
    mt_logo_get_custom_if();

    if(logo_cust_if->show_boot_logo)
    {
        logo_cust_if->show_boot_logo();
    }
    else
    {
        ///show_logo(0);
        init_fb_screen();
        fill_animation_logo(BOOT_LOGO_INDEX, mt_get_fb_addr(), (void *)mt_get_tempfb_addr(), logo_addr, phical_screen);
        mt_disp_update(0, 0, CFG_DISPLAY_WIDTH, CFG_DISPLAY_HEIGHT);
    }

    return;
}

init_fb_screen()再次初始化屏幕,然后就是调用fill_animation_logo()填充framebuffer,然后再调用mt_disp_update()更新到LCM上。

结语

至此,我们就暂时分析到这里,目的主要是为了分析我们自己移植的LCM驱动在display框架中的作用。

  • 10
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在MT6737 Android N平台上,录音到播放录音的流程可以分为以下几个步骤: 1. 打开录音设备 首先,需要打开录音设备并设置相关参数。在Android系统中,可以通过AudioRecord类来实现录音设备的打开和设置。例如: ``` int sampleRateInHz = 44100; // 采样率 int channelConfig = AudioFormat.CHANNEL_IN_MONO; // 声道数 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度 int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小 AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); recorder.startRecording(); // 开始录音 ``` 2. 录制音频数据 接下来,需要不断地读取录音设备中的音频数据,并保存到一个缓冲区中。在Android系统中,可以使用AudioRecord类的read方法来读取音频数据。例如: ``` byte[] buffer = new byte[1024]; while (isRecording) { // isRecording为标志位,表示是否正在录音 int len = recorder.read(buffer, 0, buffer.length); // 读取音频数据 // 将读取到的音频数据写入到文件或网络等 } ``` 3. 停止录音设备 当需要停止录音时,需要停止录音设备,并释放相关资源。在Android系统中,可以使用AudioRecord类的stop和release方法来实现。例如: ``` recorder.stop(); // 停止录音 recorder.release(); // 释放资源 ``` 4. 播放录音数据 在播放录音时,需要打开播放设备并设置相关参数。在Android系统中,可以使用AudioTrack类来打开播放设备并设置参数。例如: ``` int streamType = AudioManager.STREAM_MUSIC; // 音频流类型 int sampleRateInHz = 44100; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 声道数 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 采样精度 int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); // 缓冲区大小 AudioTrack player = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM); player.play(); // 开始播放 ``` 5. 播放录音数据 接下来,需要将录音数据写入到播放设备中,以实现播放录音的效果。在Android系统中,可以使用AudioTrack类的write方法来将录音数据写入到播放设备中。例如: ``` byte[] buffer = new byte[1024]; while (isPlaying) { // isPlaying为标志位,表示是否正在播放 // 从文件或网络等读取录音数据 int len = ...; // 将读取到的录音数据写入到播放设备中 player.write(buffer, 0, len); } ``` 6. 停止播放设备 当需要停止播放录音时,需要停止播放设备,并释放相关资源。在Android系统中,可以使用AudioTrack类的stop和release方法来实现。例如: ``` player.stop(); // 停止播放 player.release(); // 释放资源 ``` 以上就是录音到播放录音的流程分析。需要注意的是,在实际的开发中还需要考虑很多细节问题,例如音频格式的选择、缓冲区大小的计算、线程的管理等等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值