FFmpeg5开发入门教程18:解码内存数据并播放

上一篇介绍解码MP3文件,然后使用Qt播放。本文介绍一个从内存中获取MP3数据,然后解码播放。

流程为:

  • 从数据库中获取数据
  • 保存数据到内存中
  • 打开内存数据
  • 解码
  • 播放

为什么要从数据库获取数据呢?因为我的应用场景就是音频数据保存在数据库中。

获取数据

直接从数据库中读取就可以了

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE","test");
db.setDatabaseName("/home/jackey/Downloads/words.sqlite3");
if(db.open()){
    
    
    qDebug()<<QString("Connect to db %1 successfully.").arg(db.databaseName());
}else{
    
    
    qDebug()<<QString("Cannot connect to db: %1").arg(db.lastError().text());
    return -1;
}

QByteArray data;
QSqlQuery *query = new QSqlQuery(db);
QString sql = "select word,data from a where word=\"a\";";
if(query->exec(sql)){
    
    
    while(query->next()){
    
    
        QString word = query->value("word").toString();
        data = query->value("data").toByteArray();

        qDebug()<<QString("word: %1 data: %2").arg(word).arg(data.size());
    }
}else{
    
    
    qDebug()<<QString("Cannot get data from db: %1").arg(query->lastError().text());
}

将读取到的数据保存到内存变量QByteArray中。

打开数据

AVFormatContext *fmtCtx = avformat_alloc_context();
unsigned char* iobuffer = (uchar*)av_malloc(32768);

AVIOContext *avio = avio_alloc_context(iobuffer,32768,0,&data,fill_iobuffer,NULL,NULL);
fmtCtx->pb=avio;

使用avio一次性分配可能会使用的内存空间,iobuffer是FFmpeg使用的内存空间,待解码的原始数据保存在iobuffer中。

如何获取待解码的原始数据呢?

int fill_iobuffer(void *opaque,uint8_t *buf,int buf_size){
    
    
    QByteArray *ba = static_cast<QByteArray*>(opaque);
    if(ba==NULL || ba->isEmpty()){
    
    
        return -1;
    }

    int realsize = 0;
    if(buf_size > ba->size()){
    
    
        realsize = ba->size();
        memset(buf,0,buf_size);
    }else{
    
    
        realsize=buf_size;
    }
    memcpy(buf,ba->data(),realsize);
    ba->remove(0,realsize);
    return realsize;
}

使用fill_iobuffer回调函数作为原始数据获取的函数,返回值是复制的原始数据字节数,FFmpeg规定返回值不能为0,opaque类型为void*在此作为输入,buf是默认的FFmpeg将要使用的待解码的数据,buf_size是此次ffmpeg向回调函数请求的数据大小。

解码

能够打开数据之后,其他的部分和之前的一样。

do{
    
    
    if(avformat_open_input(&fmtCtx,NULL,NULL,NULL)<0){
    
    
        qDebug("Cannot open input file.");
        return -1;
    }
    if(avformat_find_stream_info(fmtCtx,NULL)<0){
    
    
        qDebug("Cannot find any stream in file.");
        return -1;
    }

    for(size_t i=0;i<fmtCtx->nb_streams;i++){
    
    
        if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
    
    
            aStreamIndex=(int)i;
            break;
        }
    }
    if(aStreamIndex==-1){
    
    
        qDebug("Cannot find audio stream.");
        return -1;
    }

    AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
    AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
    if(!codec){
    
    
        qDebug("Cannot find any codec for audio.");
        return -1;
    }
    codecCtx = avcodec_alloc_context3(codec);
    if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
    
    
        qDebug("Cannot alloc codec context.");
        return -1;
    }
    codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;

    if(avcodec_open2(codecCtx,codec,NULL)<0){
    
    
        qDebug("Cannot open audio codec.");
        return -1;
    }

    //设置转码参数
    uint64_t out_channel_layout = codecCtx->channel_layout;
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = codecCtx->sample_rate;
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
    //printf("out rate : %d , out_channel is: %d\n",out_sample_rate,out_channels);

    uint8_t *audio_out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);

    SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
                                             out_channel_layout,
                                             out_sample_fmt,
                                             out_sample_rate,
                                             codecCtx->channel_layout,
                                             codecCtx->sample_fmt,
                                             codecCtx->sample_rate,
                                             0,NULL);
    swr_init(swr_ctx);

    QAudioFormat audioFmt;
    audioFmt.setSampleRate(codecCtx->sample_rate);
    audioFmt.setChannelCount(out_channels);
    audioFmt.setSampleSize(16);
    audioFmt.setCodec("audio/pcm");
    audioFmt.setByteOrder(QAudioFormat::LittleEndian);
    audioFmt.setSampleType(QAudioFormat::SignedInt);

    QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
    if(!info.isFormatSupported(audioFmt)){
    
    
        audioFmt = info.nearestFormat(audioFmt);
    }
    QAudioOutput *audioOutput = new QAudioOutput(audioFmt);
    audioOutput->setVolume(100);

    QIODevice *streamOut = audioOutput->start();

    QByteArray pcmData;

    while(av_read_frame(fmtCtx,pkt)>=0){
    
    
        if(pkt->stream_index==aStreamIndex){
    
    
            if(avcodec_send_packet(codecCtx,pkt)>=0){
    
    
                while(avcodec_receive_frame(codecCtx,frame)>=0){
    
    
                    if(av_sample_fmt_is_planar(codecCtx->sample_fmt)){
    
    
                        int len = swr_convert(swr_ctx,
                                              &audio_out_buffer,
                                              MAX_AUDIO_FRAME_SIZE*2,
                                              (const uint8_t**)frame->data,
                                              frame->nb_samples);
                        if(len<=0){
    
    
                            continue;
                        }
                        int out_size = av_samples_get_buffer_size(0,
                                                                     out_channels,
                                                                     len,
                                                                     out_sample_fmt,
                                                                     1);
                        pcmData.append((char*)audio_out_buffer,out_size);
                    }
                }
            }
        }
        av_packet_unref(pkt);
    }

    bool isStop = false;
    QByteArray tempBuf = pcmData;
    int chunks=0;
    while(audioOutput &&
          audioOutput->state()!=QAudio::StoppedState &&
          audioOutput->state()!=QAudio::SuspendedState &&
          !isStop){
    
    
        //play all the time if not stop
        if(tempBuf.isEmpty()){
    
    
            tempBuf = pcmData;
            Sleep(1000);
        }
        chunks = audioOutput->bytesFree()/audioOutput->periodSize();
        if(chunks){
    
    
            if(tempBuf.length() >= audioOutput->periodSize()){
    
    
                streamOut->write(tempBuf.data(),audioOutput->periodSize());
                tempBuf = tempBuf.mid(audioOutput->periodSize());
            }else{
    
    
                streamOut->write(tempBuf);
                tempBuf.clear();
            }
        }
    }
}while(0);

av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_close(codecCtx);
avcodec_free_context(&codecCtx);
avformat_free_context(fmtCtx);

和上一篇类似,解码完成后将数据填入QIODevice播放。

完整代码在ffmpeg_Beginner中的18.audio_player_decode_from_mem_play_by_qt中。

本文首发于:FFmpeg5开发入门教程18:解码内存数据并播放

猜你喜欢

转载自blog.csdn.net/qq_26056015/article/details/126231370