很久以前,我就想在android做一個複讀機程式,用來播放我喜歡的我從網上下的《老友記》,但編一個mp3播放機當然很容易,但其它格式隻能借助其它解碼工具了,于是在網上找了一堆關于ffmpeg的資料。其它能完成音頻播放的工能,欲發幾篇文章,一來記錄下這幾天成果,二是然望能同修們有幫助。
首先的的開發環境是ubuntu,用的是ffmpeg1.0,現在網上很多資料都在很以前比較老的版本,
網上已有很多關于ffmpeg的下載下傳和在lunix底下編譯的文章,這裡不不多作介紹了,之于利用ndk編譯我以後有時間,會說下自己觀點和看法。
提取音頻程式與解碼可能參考 fmpeg-1.0/doc/examples/filtering_audio.c和ffmpeg-1.0/doc/examples/decoding_encoding.c;ffmpeg.c和ffplay太過複雜,對于我這個急于求成的外行來說不太适宜。網上有幾篇文章很好比如,
http://ushertechblog.sinaapp.com/post-24.html
上篇文章也是參照一國外部落格http://dranger.com/ffmpeg/tutorial03.html ,隻是 這晨面用的都是ffmpeg很老的版本,底下這篇用的是最新ffmpeg,
http://blog.chinaunix.net/uid-26009923-id-3384770.html
下下是我參照上面文章寫的代碼,隻改變了隻改decode_audo_frame 與audio_callback函數裡的一些内容,或許能讓感覺代碼更清晰點
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <SDL.h>
#define SDL_AUDIO_BUFFER_SIZE 1024
typedef struct PacketQueue {
AVPacketList * first_pkt, *last_pkt;
int nb_packets;
int size;
SDL_mutex *mutex; //mutex主要是用來實作資源的互拆的,跟java裡在synchronize關鍵作用有相似之處
//,雖然本和序中沒有用多線程,但sdl在播放另啟了一個線程。
SDL_cond * cond;
} PacketQueue;
PacketQueue audioq;
void packet_queue_init(PacketQueue *q) {
memset(q, 0, sizeof(PacketQueue));
q->mutex = SDL_CreateMutex();
q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1;
if (av_dup_packet(pkt) < 0)
return -1;
pkt1 = av_malloc(sizeof(AVPacketList));
if (!pkt1)
return -1;
pkt1->pkt = *pkt;
pkt1->next = NULL;
SDL_LockMutex(q->mutex);
if (!q->last_pkt)
q->first_pkt = pkt1;
else
q->last_pkt->next = pkt1;
q->last_pkt = pkt1;
q->nb_packets++;
q->size += pkt1->pkt.size;
SDL_CondSignal(q->cond);
SDL_UnlockMutex(q->mutex);
return 0;
}
int quit = 0;
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
AVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex);
for (;;) {
if (quit) {
ret = -1;
break;
}
pkt1 = q->first_pkt;
if (pkt1) {
q->first_pkt = pkt1->next;
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--;
q->size -= pkt1->pkt.size;
*pkt = pkt1->pkt;
av_free(pkt1); //這招我很贊賞,他在取去一個packet後,将上一個paket置空,而在下面的程式就不用再調用av_free操作了
ret = 1;
break;
} else if (!block) {
ret = 0;
break;
} else {
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex);
return ret;
}
//int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {
int audio_decode_frame(AVCodecContext *aCodecCtx, AVFrame *frame,
uint8_t *audio_buf) {
static AVPacket pkt_temp;
int len1, data_size, got_frame;
int new_packet;
for (;;) {
while (pkt_temp.size > 0 || (!pkt_temp.data && new_packet)) {
if (!frame) {
if (!(frame = avcodec_alloc_frame()))
return AVERROR(ENOMEM);
} else {
avcodec_get_frame_defaults(frame);
}
new_packet = 0;
len1 = avcodec_decode_audio4(aCodecCtx, frame, &got_frame,
&pkt_temp);
if (len1 < 0) {
/* if error, skip frame */
pkt_temp.size = 0;
break;
}
pkt_temp.data += len1;
pkt_temp.size -= len1;
if (got_frame <= 0) /* No data yet, get more frames */
continue;
data_size = av_samples_get_buffer_size(NULL, aCodecCtx->channels,
frame->nb_samples, aCodecCtx->sample_fmt, 1);
memcpy(audio_buf, frame->data[0], frame->linesize[0]);
/* We have data, return it and come back for more later */
return data_size;
}
if (quit)
return -1;
if ((new_packet = packet_queue_get(&audioq, &pkt_temp, 1)) < 0)
return -1;
}
}
void audio_callback(void *userdata, Uint8 *stream, int len) {
AVCodecContext *aCodecCtx = (AVCodecContext *) userdata;
//以後變量全定義成static ,確定下次循環,變量不會被初始化
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
static unsigned int audio_buf_remain_size=0; //記錄下audio_buffer剩餘資料量
static unsigned int audio_buf_total_size=0; //記錄下audio_buffer總資料量
static unsigned int audio_buf_index = 0;
int read_size; //第次送入*stream中資料的真正長度,理論值是len,但在最後一次操作實際值可能會小于len;
AVFrame *frame = NULL;
int flag=0;
while(len){
if(audio_buf_index>=audio_buf_total_size){
audio_buf_remain_size = audio_decode_frame(aCodecCtx, frame, audio_buf);
audio_buf_total_size=audio_buf_remain_size;
audio_buf_index=0;
if(audio_buf_total_size<0){
audio_buf_remain_size=audio_buf_total_size = 1024;
memset(audio_buf, 0, audio_buf_total_size);
continue;
}
}
read_size=(audio_buf_remain_size > len)? len : audio_buf_remain_size;
memcpy(stream, (uint8_t *) audio_buf + audio_buf_index, read_size);
audio_buf_index += read_size;
audio_buf_remain_size -= read_size;
stream += read_size;
len -= read_size;
}
}
int main(int argc, char *argv[]) {
AVFormatContext *pFormatCtx;
int i, videoStream, audioStream;
AVCodecContext *pCodecCtx, *aCodecCtx;
AVCodec *pCodec, *aCodec;
AVFrame *pFrame;
AVFrame *pFrameRGB;
AVPacket packet;
SDL_Event event;
int frameFinished;
int numBytes;
uint8_t *buffer;
if (argc < 2) {
printf("Please provide a movie file\n");
return -1;
}
if (SDL_Init(SDL_INIT_AUDIO)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
av_register_all();
pFormatCtx = avformat_alloc_context();
// Open video file
//if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
if (avformat_open_input(&pFormatCtx, argv[1], NULL, NULL ) != 0)
return -1; // Couldn't open file
// Retrieve stream information
if (avformat_find_stream_info(pFormatCtx, NULL ) < 0)
return -1; // Couldn't find stream information
// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, argv[1], 0);
// Find the first video stream
videoStream = -1;
audioStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO
&& videoStream < 0)
videoStream = i;
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO
&& audioStream < 0)
audioStream = i;
}
if (videoStream == -1)
return -1; // Didn't find a video stream
if (audioStream == -1)
return -1;
// Get a pointer to the codec context for the video&audio stream
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
aCodecCtx = pFormatCtx->streams[audioStream]->codec;
SDL_AudioSpec wanted_spec, spec;
wanted_spec.freq = aCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;
if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
fprintf(stderr, "SDL_OpenAudio:%s\n", SDL_GetError());
return -1;
}
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL ) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Find the decoder for the video stream
aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if (aCodec == NULL ) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Open codec
if (avcodec_open2(aCodecCtx, aCodec, NULL ) < 0)
return -1; // Could not open codec
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, NULL ) < 0)
return -1; // Could not open codec
packet_queue_init(&audioq);
SDL_PauseAudio(0);
//Set up a screen
while (av_read_frame(pFormatCtx, &packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index == audioStream) {
packet_queue_put(&audioq, &packet);
} else {
av_free_packet(&packet);
}
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit = 1;
SDL_Quit();
exit(0);
break;
default:
break;
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
// Free the RGB image
for (;;) {
getchar(); //這個是用來阻塞主線程,也可以用來接收鍵盤指令,比如暫停播放之類的
break;
}
// Close the codec
avcodec_close(pCodecCtx);
avcodec_close(aCodecCtx);
// Close the video file
avformat_close_input(&pFormatCtx);
return 0;
}