1. 程式人生 > >ffmpeg之G711解析成pcm

ffmpeg之G711解析成pcm

ffmpeg在碼流轉換上面實在是強大,今天實驗了一下把G711音訊專成PCM的音訊,並最終實驗成功。

第一步:尋找解碼器,若格式不支援,則無法轉碼

	codec = avcodec_find_decoder(AV_CODEC_ID_PCM_ALAW);
	if (!codec) {
		fprintf(stderr, "Codec not found\n");
		return false;
	}

提醒:G711A的解碼ID為AV_CODEC_ID_PCM_ALAW

第二步:建立解碼上下文,主要保持解碼相關的資訊

	c = avcodec_alloc_context3(codec);
	if (!c) {
		fprintf(stderr, "Could not allocate audio codec context\n");
		return false;
	}
	c->sample_fmt = sample_fmt;
	c->sample_rate = sample_rate;
	c->channels = channels;

需要設定3個引數,不然後面開啟解碼器會失敗

sample_fmt:取樣格式,標識含義8位,16位等取樣

sample_rate:取樣頻率

channels:取樣通道數

第三步:開啟解碼器

	if (avcodec_open2(c, codec, NULL) < 0) {
		fprintf(stderr, "Could not open codec\n");
		return false;
	}

第四步:輸入碼流

ret = avcodec_send_packet(c, packet);

第五步:輸出解碼結果

int ret = avcodec_receive_frame(c, frame);

整個過程大約分為這5個步驟。

另外通過研究,有幾個重要的關於記憶體的發現:

由於輸入解碼的資料格式是AVPacket,而我拿到的資料確是位元組流,需要把位元組流轉換成AVPacket型別。

這裡作了如下處理:

	packet->size = iSize;
	packet->data = (uint8_t *)av_malloc(packet->size);
	memcpy(packet->data, pData, iSize);
	ret = av_packet_from_data(packet, packet->data, packet->size);
	if (ret <0)
	{
		fprintf(stderr, "av_packet_from_data error \n");
		av_free(packet->data);
		return;
	}
	ret = avcodec_send_packet(c, packet);
	av_packet_unref(packet);

通過函式av_packet_from_data生產了AVPacket型別資料,但是需要注意在使用完之後,需要呼叫av_packet_unref進行釋放記憶體,不然會存在記憶體洩漏,有人會問上面的記憶體是通過av_malcoc生成的,我們還需要釋放嗎?我通過實驗發現呼叫av_packet_unref之後,就不需要再呼叫av_free來進行釋放了,ffmpeg好像自己已經釋放了。

另外關於解碼出來的音訊,需要注意他的存放再uint8_t *data[AV_NUM_DATA_POINTERS];裡面的,這是一個數組,每個通道一維。由於pcm保持多通道根據取樣率進行順序保持,所以保持位pcm需要處理一下:

int iCopyPos = 0;
	for(int i = 0; i < frame->nb_samples; i++)
	{
		for (int ch = 0; ch < c->channels; ch++)
		{
			memcpy(m_pOutData + iCopyPos,frame->data[ch] + data_size * i, data_size);
			iCopyPos = iCopyPos + data_size;
		}
	}

最後,為了便於大家學習和交流,我採用vs2017開發,上傳示例如下連線: