当前位置:  编程技术>移动开发
本页文章导读:
    ▪ffmpeg解码audio文件的时分,实现seek功能(即解码指定时间位置的数据)        ffmpeg解码audio文件的时候,实现seek功能(即解码指定时间位置的数据)ffmpeg解码的时候,往往需要seek功能,直接跳到所关心的时间位置进行解码。比如制作播放器的时候,可以seek到任意位置.........
    ▪ Unity3D开发(5):Unity3D 4.x 使用Mecanim实现连击        Unity3D开发(五):Unity3D 4.x 使用Mecanim实现连击Unity3D 4.x 版本之后提供了一种新的动画机制Mecanim,虽然目前还支持之前的Animation,但看到Unity3D 4.3 预览版里Sprite的动画也是基于Animator的,可.........
    ▪ ffmpeg解码音频数据时,开展重采样(即改变文件原有的采样率)       ffmpeg解码音频数据时,进行重采样(即改变文件原有的采样率)我们使用ffmpeg解码音频的时候,往往需要改变原音频的采样率,即需要重采样。 比如一音乐文件的采样率22050,而播放端往往.........

[1]ffmpeg解码audio文件的时分,实现seek功能(即解码指定时间位置的数据)
    来源: 互联网  发布时间: 2014-02-18
ffmpeg解码audio文件的时候,实现seek功能(即解码指定时间位置的数据)

ffmpeg解码的时候,往往需要seek功能,直接跳到所关心的时间位置进行解码。比如制作播放器的时候,可以seek到任意位置播放。

我们可以通过ffmpeg API av_seek_frame()来定位解码的位置,该函数能根据设定的时间,定位到该时间对应的帧位置。随后我们继续使用API av_read_frame()/avcodec_decode_audio3()读取帧、并解码。

需要注意的是,ffmpeg内部的时间单位为AV_TIME_BASE,所以当我们需要定位到n秒的时候,需要将n乘以AV_TIME_BASE。

 

seek函数实现如下:

/*seek to start_time, in s*/
int internal_seek(long start_time)
{
	int64_t seek_pos = 0;

	LOGD("enter internal_seek(), seek to time:%d (s)", start_time);

	if (start_time < 0)
		return -1;

	seek_pos = start_time*AV_TIME_BASE;
	if (m_format_ctx->start_time != AV_NOPTS_VALUE)
		seek_pos += m_format_ctx->start_time;

	if (av_seek_frame(m_format_ctx, -1, seek_pos, AVSEEK_FLAG_BACKWARD) < 0)
	{
		LOGE("%s,  av_seek_frame() seek to %.3f failed!", __FUNCTION__, (double)seek_pos/AV_TIME_BASE);
		return -2;
	}

	LOGD("exit internal_seek()");
	return 0;
}


 


    
[2] Unity3D开发(5):Unity3D 4.x 使用Mecanim实现连击
    来源: 互联网  发布时间: 2014-02-18
Unity3D开发(五):Unity3D 4.x 使用Mecanim实现连击

Unity3D 4.x 版本之后提供了一种新的动画机制Mecanim,虽然目前还支持之前的Animation,但看到Unity3D 4.3 预览版里Sprite的动画也是基于Animator的,可知Mecanim将会是以后动画播放的趋势!

Mecanim是一种基于状态机的结构,不同的状态表示一个动作(或者多个动作融合,或者一个子状态机),状态之间使用一种称为Transition的组件关联,Transition中可以设定一些条件,当在Transition“源状态”下,满足其条件之后,将自动跳转到Transition“目的状态”!(具体用法请参照Unity3D手册Mecanim)

在一些ARPG的游戏中,比如 端游的DNF和闯关类的街机游戏,连续按攻击键都会触发一套组合攻击,在这个过程中敌人一般是硬直状态的,而且组合攻击的最后一下一般是重击,这样做既增加了连击带来的爽快感,又促使玩家在攻击时采取一定的攻击策略!

Mecanim的状态机设计就很方便的使开发者实现了这一效果!


1.动画状态机

这里只用4个动作描述,待命状态(Idle),攻击1状态(AtkSlice),攻击2状态(AtkStab),攻击3状态(AtkCleave),3个攻击状态分别表示连续按下“普通攻击键”时触发的状态,是有先后顺序关系的,即如果在待命状态下按下“攻击键”,则进入攻击1状态;如果在攻击1状态下继续按下“攻击键”,则进入攻击2状态,如果不按下“攻击键”,则回到待命状态;攻击2状态到攻击3状态同理;攻击3状态认为是重击,即连击结束,回到待命状态!

状态图如下


我希望能以最少的代码和设置完成这个功能,所以只添加了一个状态机参数 ActionCMD,假设 ActionCMD = 1 为进入攻击状态参数

(1)在任意3种攻击状态下,不再继续按下“攻击键”,则回到待命状态,即上图 3条白色Transition (2)在待命,攻击1,攻击2状态下,继续按下“攻击键”,则跳转到连击状态(即下一个攻击状态),即上图3条蓝色Transition

2.代码实现 (1)状态机设置完成之后,要做的就是在代码中完成对当前状态的判断,以及对状态参数的设置,用以完成动画状态机的切换!

// 使用字符串变量保存当前状态,避免多处引用写错
    private static readonly string IdleState = "BaseLayer.Idle";
    private static readonly string AtkSliceState = "BaseLayer.AtkSlice";
    private static readonly string AtkStabState = "BaseLayer.AtkStab";
    private static readonly string AtkCleave = "BaseLayer.AtkCleave";
    // 动画状态机参数Key
    private static readonly string ActionCMD = "ActionCMD";

    private Animator animator = null;
	// 当前连击数(即 玩家按下攻击键的次数)
    private int curComboCount = 0;

(2)在Start() 中获取Animator组件引用

(3)在Update() 中根据当前状态和输入参数促使状态切换

void Update()
    {
        AnimatorStateInfo stateInfo = this.animator.GetCurrentAnimatorStateInfo(0);
        if (!stateInfo.IsName(IdleState))
        {
			// 每次设置完参数之后,都应该在下一帧开始时将参数设置清空,避免连续切换
            this.animator.SetInteger(ActionCMD, 0);
        }

        if (stateInfo.IsName(AtkSliceState) && (stateInfo.normalizedTime > 0.6f) && (this.curComboCount == 2))
        {
			// 当在攻击1状态下,并且当前状态运行了0.6正交化时间(即动作时长的60%),并且用户在攻击1状态下又按下了“攻击键”
            this.animator.SetInteger(ActionCMD, 1);
        }
        if (stateInfo.IsName(AtkStabState) && (stateInfo.normalizedTime > 0.8f) && (this.curComboCount == 3))
        {
			// 挡在攻击2状态下(同理攻击1状态)
            this.animator.SetInteger(ActionCMD, 1);
        }

        if (Input.GetKeyUp(KeyCode.J))
        {
			// 监听用户输入(假设J键为攻击键)
            Attack();
        }
    }

void Attack()
    {
        AnimatorStateInfo stateInfo = this.animator.GetCurrentAnimatorStateInfo(0);
        if (stateInfo.IsName(IdleState))
        {
			// 在待命状态下,按下攻击键,进入攻击1状态,并记录连击数为1
            this.animator.SetInteger(ActionCMD, 1);
            this.curComboCount = 1;
        }
        else if (stateInfo.IsName(AtkSliceState))
        {
			// 在攻击1状态下,按下攻击键,记录连击数为2(切换状态在Update()中)
            this.curComboCount = 2;
        }
        else if (stateInfo.IsName(AtkStabState))
        {
			// 在攻击2状态下,按下攻击键,记录连击数为3(切换状态在Update()中)
            this.curComboCount = 3;
        }
    }	


这里需要注意的是,在Update() 中使用的0.6和0.8 应该分别小于当前状态跳转到待命状态的参数Exit Time(这个时间也是正交化的)



    
[3] ffmpeg解码音频数据时,开展重采样(即改变文件原有的采样率)
    来源: 互联网  发布时间: 2014-02-18
ffmpeg解码音频数据时,进行重采样(即改变文件原有的采样率)

我们使用ffmpeg解码音频的时候,往往需要改变原音频的采样率,即需要重采样。

比如一音乐文件的采样率22050,而播放端往往是固定的采样率,比如44100。在这种情况下,如果把解码出来的数据直接播放,会产生快进的效果。这个时候就需要对解码出来的数据作一次重采样,将数据转化为44100采样率下的数据,才能正确播放。

ffmpeg提供了一组用来重采样的API,主要如下:

/**
 *  Initialize audio resampling context.
 *
 * @param output_channels  number of output channels
 * @param input_channels   number of input channels
 * @param output_rate      output sample rate
 * @param input_rate       input sample rate
 * @param sample_fmt_out   requested output sample format
 * @param sample_fmt_in    input sample format
 * @param filter_length    length of each FIR filter in the filterbank relative to the cutoff frequency
 * @param log2_phase_count log2 of the number of entries in the polyphase filterbank
 * @param linear           if 1 then the used FIR filter will be linearly interpolated
                           between the 2 closest, if 0 the closest will be used
 * @param cutoff           cutoff frequency, 1.0 corresponds to half the output sampling rate
 * @return allocated ReSampleContext, NULL if error occured
 */
ReSampleContext *av_audio_resample_init(int output_channels, int input_channels,
                                        int output_rate, int input_rate,
                                        enum AVSampleFormat sample_fmt_out,
                                        enum AVSampleFormat sample_fmt_in,
                                        int filter_length, int log2_phase_count,
                                        int linear, double cutoff);

int audio_resample(ReSampleContext *s, short *output, short *input, int nb_samples);

/**
 * Free resample context.
 *
 * @param s a non-NULL pointer to a resample context previously
 *          created with av_audio_resample_init()
 */
void audio_resample_close(ReSampleContext *s);


函数av_audio_resample_init()用来初始化重采样的参数,前4个参数很好理解;后6个参数基本上是使用缺省参数,分别为:

AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16,16, 10, 0, 1
函数audio_resample()用来重采样,前3个参数都好理解,最后一个参数是指“原数据的采样个数”,而不是input的bytes数。该函数的返回值也是采样个数,不过是重采样之后的。

函数audio_resample_close()用来清理重采样时分配的资源。

相关代码如下:

初始化

	// need to do re-sample
	if (m_codec_ctx->sample_rate != m_out_samplerate)
	{
		LOGW("%s, need re-sample, initialize re-sample engine! out channels:%d, out sample rate:%d hz, in channels:%d, in sample rate:%d",__FUNCTION__, 2, m_out_samplerate, m_codec_ctx->channels, m_codec_ctx->sample_rate);
		m_resample_engine = av_audio_resample_init(	2, m_codec_ctx->channels, m_out_samplerate, m_codec_ctx->sample_rate, AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16, 16, 10, 0, 1);
	}

重采样代码

frame_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
memset(m_audio_buff, 0, AVCODEC_MAX_AUDIO_FRAME_SIZE);
decoded_len = avcodec_decode_audio3(m_codec_ctx, (short *)m_audio_buff, &frame_size, &m_avpkt);
LOGI("%s, current decoded size:%d", __FUNCTION__, decoded_len);
if (decoded_len > 0)
{
	m_avpkt.size -= decoded_len;
	m_avpkt.data += decoded_len;

	decoded_audio_len = frame_size;
	valid_data_pointer = m_audio_buff;

	// need to re-sample
	if (m_resample_engine)
	{
		// convert byte to short
		int after_resampled_len = 0;
		int before_resampled_len = frame_size/(2 * m_codec_ctx->channels);
		memset(m_audio_resampled_buff, 0, AVCODEC_MAX_AUDIO_FRAME_SIZE);
		after_resampled_len = audio_resample(m_resample_engine, (short *)m_audio_resampled_buff, (short *)m_audio_buff, before_resampled_len);
		LOGI("%s, re-sampled! length in:%d, length out:%d", __FUNCTION__, before_resampled_len, after_resampled_len);
		decoded_audio_len = after_resampled_len * 2 * 2;	//convert short to byte, and 2 channels
		valid_data_pointer = m_audio_resampled_buff;
	}

      	memcpy(buff+copied_len, valid_data_pointer, decoded_audio_len);
      	copied_len += decoded_audio_len;
      	LOGI("%s, copy1, %d bytes has copied to output buff, total:%d!", __FUNCTION__, decoded_audio_len, copied_len);                                                                                                                        }

释放代码

if (m_resample_engine)
{
	audio_resample_close(m_resample_engine);
	m_resample_engine = 0;
}

 


    
最新技术文章:
▪Android开发之登录验证实例教程
▪Android开发之注册登录方法示例
▪Android获取手机SIM卡运营商信息的方法
▪Android实现将已发送的短信写入短信数据库的...
▪Android发送短信功能代码
▪Android根据电话号码获得联系人头像实例代码
▪Android中GPS定位的用法实例
▪Android实现退出时关闭所有Activity的方法
▪Android实现文件的分割和组装
▪Android录音应用实例教程
▪Android双击返回键退出程序的实现方法
▪Android实现侦听电池状态显示、电量及充电动...
▪Android获取当前已连接的wifi信号强度的方法
▪Android实现动态显示或隐藏密码输入框的内容
▪根据USER-AGENT判断手机类型并跳转到相应的app...
▪Android Touch事件分发过程详解
▪Android中实现为TextView添加多个可点击的文本
▪Android程序设计之AIDL实例详解
▪Android显式启动与隐式启动Activity的区别介绍
▪Android按钮单击事件的四种常用写法总结
▪Android消息处理机制Looper和Handler详解
▪Android实现Back功能代码片段总结
▪Android实用的代码片段 常用代码总结
▪Android实现弹出键盘的方法
▪Android中通过view方式获取当前Activity的屏幕截...
▪Android提高之自定义Menu(TabMenu)实现方法
▪Android提高之多方向抽屉实现方法
▪Android提高之MediaPlayer播放网络音频的实现方法...
▪Android提高之MediaPlayer播放网络视频的实现方法...
▪Android提高之手游转电视游戏的模拟操控
 


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3