FFmpeg4入门18:解码内存数据并播放

索引地址:系列教程索引地址

上一篇:FFmpeg4入门17:软件解码音频并使用QAudioOutput播放

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

流程为:

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

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

获取数据

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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中。

打开数据

1
2
3
4
5
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中。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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向回调函数请求的数据大小。

解码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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中。

下一篇:FFmpeg4入门19:pcm编码为mp3


FFmpeg4入门18:解码内存数据并播放
https://feater.top/ffmpeg/ffmpeg-decode-data-in-memory-and-play-with-qt
作者
JackeyLea
发布于
2021年12月27日
许可协议