美文网首页
ffmpeg 音频播放器相关

ffmpeg 音频播放器相关

作者: 曾大稳丶 | 来源:发表于2018-07-05 09:41 被阅读0次
    每秒理论PCM大小
    每秒理论PCM大小 = 采样率 * 声道数 * 位数/8
    

    比如:

    //44100hz 立体声 16bit
    int s_time = 44100 * 2 * 16/8;
    
    获取总时长
    duration = pFormatCtx->duration / AV_TIME_BASE;
    
    获取当前AVframe时间
    
    AVRational time_base = pFormatCtx->streams[audio_index]->time_base
    double now_time = frame->pts * av_q2d(time_base);
    
    
    获取当前播放时间

    因为每一个AVframepts不一定都有,所以就需要自己手维护一个当前时间的变量

    公式:PCM实际数据大小 / 每秒理论PCM大小;
    clock += buffersize / ((double)(sample_rate * 2 * 2));
    
    

    伪代码如下:

    AVFormatContext *pFormatCtx = NULL;
    //采样率
    int sample_rate =0;
    //当前总时长
    int duration = 0;
    
    AVRational time_base =NULL;
    //当前AvFrame时间
    int now_time=0;
    //当前播放时长
    int clock = 0;
    //上次播放时长标识
    int last_time=0;
    
    uint8_t *buffer = NULL;
    int data_size=0;
    
    
    //获取到 pFormatCtx
    ...
    for(int i = 0; i < pFormatCtx->nb_streams; i++)
        {
            if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//得到音频流
            {
                    ...
                    sample_rate = pFormatCtx->streams[i]->codecpar->sample_rate;
                    duration= pFormatCtx->duration / AV_TIME_BASE;
                    time_base = pFormatCtx->streams[i]->time_base;
            }
    }
    
    //解码数据  
    ...
        
      int nb = swr_convert(
                    swr_ctx,
                    &buffer,
                    avFrame->nb_samples,
                    (const uint8_t **) avFrame->data,
                    avFrame->nb_samples);
    
       int out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
       data_size = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    
        now_time = avFrame->pts * av_q2d(time_base);
        if(now_time < clock){
           now_time = clock;
        }
        clock = now_time;
    
    
    //解码数据之后数据封装在 buffer,播放的时候
    ...
    
    if(data_size>0){
        //size/(采样率(44100hz) * 立体声*16bit/8) 
        clock += data_size / ((double)(sample_rate * 2 * 2));
        //设置一个回掉最小相差值
        if(clock - last_tiem >= 0.1) {
            last_tiem = clock;
            //回调应用层
           callJava->onCallTimeInfo(CHILD_THREAD,clock, duration);
        } 
    }
    
    
    解码播放流程思路

    采用多线程,生产者消费者模型,AVPacket入队,然后AVPacket出队解码播放,播放采用OpenSLES

    release内存回收

    当我们release的时候,我们需要注意

    1. 为了确保线程完全退出,我们最好是sleep个几十毫秒,然后在释放相关内存,但是最好的是使用pthred_join来同步线程退出。
    2. 有可能初始化未准备完毕我们就调用release,这时候最好是在初始化准备和
      release加个线程锁。
    3. 初始化的时候有可能avformat_open_input打开网络链接,网络很卡,所以我们需要为pFormatCtx加入一个interrupt_callback来及时响应
    int avformat_callback(void *ctx)
    {
        WlFFmpeg *fFmpeg = (WlFFmpeg *) ctx;
        if(fFmpeg->playstatus->exit)
        {
            return AVERROR_EOF;
        }
        return 0;
    }
    
    
    pFormatCtx->interrupt_callback.callback = avformat_callback;
    pFormatCtx->interrupt_callback.opaque = this;
    
    
    
    暂停,继续,停止播放,播放完成

    暂停播放,继续播放采用OpenSLES的相关api,播放完成则在播放完毕的时候回掉即可

    seek功能

    seek的时候设置标志位并加锁,清空队列,标志位判断是否继续av_read_frameseek完毕释放锁,还原标识位。即可重新读取最新数据

    
    seek = true;
    queue->clearAvpacket();
    
    pthread_mutex_lock(&seek_mutex);
    int64_t rel = secs * AV_TIME_BASE;
    //重置内部解码器状态/刷新内部缓冲区
     avcodec_flush_buffers(avCodecContext);
    //主要是这个函数
    avformat_seek_file(pFormatCtx, -1, INT64_MIN, rel, INT64_MAX, 0);
    pthread_mutex_unlock(&seek_mutex);
    seek = false;
    
    pthread_mutex_lock(&seek_mutex);
    ret = av_read_frame(pFormatCtx, packet);
    pthread_mutex_unlock(&seek_mutex);
    
    
    音量,声道切换

    采用OpenSLES的相关api

    播放变速变调

    OpenSL ES可以实现变速播放,但是再改变速度的同时也改变了音调,这
    种体验是不好的。所以采用SoundTouch来实现,在播放的时候,对原始数据重新进行计算即可

    计算pcm分贝大小
    
    //char*是为了都转换成字节来处理 
    int WlAudio::getPCMDB(char *pcmcata, size_t pcmsize) {
        int db = 0;
        short int pervalue = 0;
        double sum = 0;
        for (int i = 0; i < pcmsize; i += 2) {
            memcpy(&pervalue, pcmcata + i, 2);
            sum += abs(pervalue);
        }
        sum = sum / (pcmsize / 2);
        if (sum > 0) {
            db = (int) 20.0 * log10(sum);
        }
        return db;
    }
    
    
    性能优化
    1. 由于解码用到了while循环,而不加睡眠的while循环会使CPU使用率提高30%左右,
      因此我们需要为解码线程加上一定的睡眠时间来降低CPU使用率。
    2. 停止时回收创建的内存空间。
    一个AVPacket对应多个AVFrame 比如.ape格式的

    这种情况就需要在解码的时候,设置一个标识来判断不停的解析AVPacket,avcodec_send_packet(avCodecContext, avPacket)之后不停的avcodec_receive_frame,知道读取完毕在设置标识。

    一个AVPacket对应多个AVFrame引发的seek问题

    由于一个AVPacket里面有多个AVFrame,当seek时,FFmpeg解码器中还残留AVFrame,所以会导致seek后,不能立即播放当前音乐。
    解决方案就是seek的时候调用

    avcodec_flush_buffers(avCodecContext)
    

    相关文章

      网友评论

          本文标题:ffmpeg 音频播放器相关

          本文链接:https://www.haomeiwen.com/subject/rtmuuftx.html