1. 程式人生 > >FFmpeg淺嘗輒止(四)——音訊的解碼和編碼

FFmpeg淺嘗輒止(四)——音訊的解碼和編碼

音訊和視訊其實是一樣的,在檔案中尋找音訊流,然後解壓出來,得到音訊幀的資料,同樣也可以按照設定的編碼格式進行壓縮,我這裡把音訊的解碼和編碼做成了兩個工程,也是直接上程式碼:

#include <stdio.h>
#include <stdlib.h>

extern "C"
{
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libswscale\swscale.h>
}

int main(char arg,char *argv[])
{
	char *filename = argv[1];

	av_register_all();	//註冊所有可解碼型別
	AVFormatContext *pInFmtCtx=NULL;	//檔案格式
	AVCodecContext *pInCodecCtx=NULL;	//編碼格式 
	if (av_open_input_file(&pInFmtCtx, filename, NULL, 0, NULL)!=0)	//獲取檔案格式
		printf("av_open_input_file error\n");
	if (av_find_stream_info(pInFmtCtx) < 0)	//獲取檔案內音視訊流的資訊
		printf("av_find_stream_info error\n");

	unsigned int j;
	// Find the first audio stream

	int audioStream = -1;
	for (j=0; j<pInFmtCtx->nb_streams; j++)	//找到音訊對應的stream
	{
		if (pInFmtCtx->streams[j]->codec->codec_type == CODEC_TYPE_AUDIO)
		{
			audioStream = j;
			break;
		}
	}
	if (audioStream == -1)
	{
		printf("input file has no audio stream\n");
		return 0; // Didn't find a audio stream
	}
	printf("audio stream num: %d\n",audioStream);
	pInCodecCtx = pInFmtCtx->streams[audioStream]->codec; //音訊的編碼上下文
	AVCodec *pInCodec = NULL;

	pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id); //根據編碼ID找到用於解碼的結構體
	if (pInCodec == NULL)
	{
		printf("error no Codec found\n");
		return -1 ; // Codec not found
	}

	if(avcodec_open(pInCodecCtx, pInCodec)<0)//將兩者結合以便在下面的解碼函式中呼叫pInCodec中的對應解碼函式
	{
		printf("error avcodec_open failed.\n");
		return -1; // Could not open codec

	}

	static AVPacket packet;

	printf(" bit_rate = %d \r\n", pInCodecCtx->bit_rate);
	printf(" sample_rate = %d \r\n", pInCodecCtx->sample_rate);
	printf(" channels = %d \r\n", pInCodecCtx->channels);
	printf(" code_name = %s \r\n", pInCodecCtx->codec->name);
	printf(" block_align = %d\n",pInCodecCtx->block_align);

	uint8_t *pktdata;
	int pktsize;
	int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;
	uint8_t * inbuf = (uint8_t *)malloc(out_size);
	FILE* pcm;
	pcm = fopen("result.pcm","wb");
	long start = clock();
	while (av_read_frame(pInFmtCtx, &packet) >= 0)//pInFmtCtx中呼叫對應格式的packet獲取函式
	{
		if(packet.stream_index==audioStream)//如果是音訊
		{
			pktdata = packet.data;
			pktsize = packet.size;
			while(pktsize>0)
			{
				out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;
				//解碼
				int len = avcodec_decode_audio2(pInCodecCtx, (short*)inbuf, &out_size, pktdata, pktsize);
				if (len < 0)
				{
					printf("Error while decoding.\n");
					break;
				}
				if(out_size > 0)
				{
					fwrite(inbuf,1,out_size,pcm);//pcm記錄
					fflush(pcm);
				}
				pktsize -= len;
				pktdata += len;
			}
		} 
		av_free_packet(&packet);
	}
	long end = clock();
	printf("cost time :%f\n",double(end-start)/(double)CLOCKS_PER_SEC);
	free(inbuf);
	fclose(pcm);
	if (pInCodecCtx!=NULL)
	{
		avcodec_close(pInCodecCtx);
	}
	av_close_input_file(pInFmtCtx);

	return 0;
}

解碼後的檔案儲存為result.pcm中,現在用這個檔案壓縮出新的音訊檔案,程式碼如下:
void main()
{
	int16_t *samples;
	uint8_t *audio_outbuf;
	int audio_outbuf_size;
	int audio_input_frame_size;
	double audio_pts;
	
	const char* filename = "test.wav";
	FILE *fin = fopen("result.pcm", "rb"); //音訊原始檔 
	AVOutputFormat *fmt;
	AVFormatContext *oc;
	AVStream * audio_st;
	av_register_all();
	fmt = guess_format(NULL, filename, NULL);
	oc = av_alloc_format_context();
	oc->oformat = fmt;
	snprintf(oc->filename, sizeof(oc->filename), "%s", filename);
	audio_st = NULL;

	if (fmt->audio_codec != CODEC_ID_NONE)
	{
		AVCodecContext *c;
		audio_st = av_new_stream(oc, 1);
		c = audio_st->codec;
		c->codec_id = fmt->audio_codec;
		c->codec_type = CODEC_TYPE_AUDIO;
		c->bit_rate = 128000;
		c->sample_rate = 44100;
		c->channels = 2;
	}
	if (av_set_parameters(oc, NULL) < 0)
	{
		return;
	}
	dump_format(oc, 0, filename, 1);
	if (audio_st)
	{
		AVCodecContext* c;
		AVCodec* codec;
		c = audio_st->codec;
		codec = avcodec_find_encoder(c->codec_id);
		avcodec_open(c, codec);
		audio_outbuf_size = 10000;
		audio_outbuf = (uint8_t*)av_malloc(audio_outbuf_size);
		if (c->frame_size <= 1)
		{
			audio_input_frame_size = audio_outbuf_size / c->channels;
			switch (audio_st->codec->codec_id)
			{
			case CODEC_ID_PCM_S16LE:
			case CODEC_ID_PCM_S16BE:
			case CODEC_ID_PCM_U16LE:
			case CODEC_ID_PCM_U16BE:
				audio_input_frame_size >>= 1;
				break;
			default:
				break;
			}
		}
		else
		{
			audio_input_frame_size = c->frame_size;
		}
		samples = (int16_t*)av_malloc(audio_input_frame_size*2*c->channels);
	}
	if (!fmt->flags & AVFMT_NOFILE)
	{
		if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0)
		{
			return;
		}
	}
	av_write_header(oc);
	for (;;)
	{
		if (audio_st)
		{
			audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den;
		}
		else
		{
			audio_pts = 0.0;
		}
		if (!audio_st || audio_pts >= 360.0)
		{
			break;
		}
		if (fread(samples, 1, audio_input_frame_size*2*audio_st->codec->channels, fin) <= 0)
		{
			break;
		}
		AVCodecContext* c;
		AVPacket pkt;
		av_init_packet(&pkt);
		c = audio_st->codec;
		pkt.size = avcodec_encode_audio(c, audio_outbuf, audio_outbuf_size, samples);
		pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, audio_st->time_base);
		pkt.flags |= PKT_FLAG_KEY;
		pkt.stream_index = audio_st->index;
		pkt.data = audio_outbuf;
		if (av_write_frame(oc, &pkt) != 0)
		{
			return;
		}
	}
	if (audio_st)
	{
		avcodec_close(audio_st->codec);
		av_free(samples);
		av_free(audio_outbuf);
	}
	av_write_trailer(oc);
	for (int i=0; i<oc->nb_streams; i++)
	{
		av_freep(&oc->streams[i]->codec);
		av_freep(&oc->streams[i]);
	}
	if (!(fmt->flags & AVFMT_NOFILE))
	{
		url_fclose(oc->pb);
	}
	av_free(oc);
	fclose(fin);
}

對應的下載連結:

至此,我已經實現了視訊的解碼編碼,音訊的解碼編碼,相信有心人肯定可以在此基礎上實現視訊音訊的同步壓縮,不過要再看一些網上的資料,也是很輕鬆的。至於視訊的顯示播放,大多數是要結合SDL實現,由於我只是淺嘗輒止,只實現了圖片的顯示和簡單的視訊播放,比起我之前推薦的幾個連結是弱爆了的,就不寫下去了,這個系列就到這裡吧,希望能給像我一樣的一些入門級選手點幫助。也歡迎大神來多多指教。