📄 tutorial03.java
字号:
## An ffmpeg and SDL TutorialPage 1 2 3 4 5 6 7 8 End Prev Home Next Printable version Text version## Tutorial 03: Playing SoundCode: tutorial03.c### AudioSo now we want to play sound. SDL also gives us methods for outputting sound.The SDL_OpenAudio() function is used to open the audio device itself. It takesas arguments an SDL_AudioSpec struct, which contains all the information aboutthe audio we are going to output. Before we show how you set this up, let's explain first about how audio ishandled by computers. Digital audio consists of a long stream of **samples**.Each sample represents a value of the audio waveform. Sounds are recorded at acertain **sample rate**, which simply says how fast to play each sample, andis measured in number of samples per second. Example sample rates are 22,050and 44,100 samples per second, which are the rates used for radio and CDrespectively. In addition, most audio can have more than one channel forstereo or surround, so for example, if the sample is in stereo, the sampleswill come 2 at a time. When we get data from a movie file, we don't know howmany samples we will get, but ffmpeg will not give us partial samples - thatalso means that it will not split a stereo sample up, either. SDL's method for playing audio is this: you set up your audio options: thesample rate (called "freq" for **frequency** in the SDL struct), number ofchannels, and so forth, and we also set a callback function and userdata. Whenwe begin playing audio, SDL will continually call this callback function andask it to fill the audio buffer with a certain number of bytes. After we putthis information in the SDL_AudioSpec struct, we call SDL_OpenAudio(), whichwill open the audio device and give us back _another_ AudioSpec struct. Theseare the specs we will _actually_ be using -- we are not guaranteed to get whatwe asked for! ### Setting Up the AudioKeep that all in your head for the moment, because we don't actually have anyinformation yet about the audio streams yet! Let's go back to the place in ourcode where we found the video stream and find which stream is the audiostream. // Find the first video stream videoStream=-1; audioStream=-1; for(i=0; i < pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO && videoStream < 0) { videoStream=i; } if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO && audioStream < 0) { audioStream=i; } } if(videoStream==-1) return -1; // Didn't find a video stream if(audioStream==-1) return -1; From here we can get all the info we want from the AVCodecContext from thestream, just like we did with the video stream: AVCodecContext *aCodecCtx; aCodecCtx=pFormatCtx->streams[audioStream]->codec; Contained within this codec context is all the information we need to set upour audio: 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; } Let's go through these: * freq: The sample rate, as explained earlier. * format: This tells SDL what format we will be giving it. The "S" in"S16SYS" stands for "signed", the 16 says that each sample is 16 bits long,and "SYS" means that the endian-order will depend on the system you are on.This is the format that avcodec_decode_audio2 will give us the audio in. * channels: Number of audio channels. * silence: This is the value that indicated silence. Since the audio issigned, 0 is of course the usual value. * samples: This is the size of the audio buffer that we would like SDL togive us when it asks for more audio. A good value here is between 512 and8192; ffplay uses 1024. * callback: Here's where we pass the actual callback function. We'll talkmore about the callback function later. * userdata: SDL will give our callback a void pointer to any user data thatwe want our callback function to have. We want to let it know about our codeccontext; you'll see why.Finally, we open the audio with SDL_OpenAudio. If you remember from the previous tutorials, we still need to open the audiocodec itself. This is straightforward: AVCodec *aCodec; aCodec = avcodec_find_decoder(aCodecCtx->codec_id); if(!aCodec) { fprintf(stderr, "Unsupported codec!\n"); return -1; } avcodec_open(aCodecCtx, aCodec); ### QueuesThere! Now we're ready to start pulling audio information from the stream. Butwhat do we do with that information? We are going to be continuously gettingpackets from the movie file, but at the same time SDL is going to call thecallback function! The solution is going to be to create some kind of globalstructure that we can stuff audio packets in so our audio_callback hassomething to get audio data from! So what we're going to do is to create a**queue** of packets. ffmpeg even comes with a structure to help us with this:AVPacketList, which is just a linked list for packets. Here's our queuestructure: typedef struct PacketQueue { AVPacketList *first_pkt, *last_pkt; int nb_packets; int size; SDL_mutex *mutex; SDL_cond *cond; } PacketQueue; First, we should point out that nb_packets is not the same as size -- sizerefers to a byte size that we get from packet->size. You'll notice that wehave a mutex and a condtion variable in there. This is because SDL is runningthe audio process as a separate thread. If we don't lock the queue properly,we could really mess up our data. We'll see how in the implementation of thequeue. Every programmer should know how to make a queue, but we're includingthis so you can learn the SDL functions. First we make a function to initialize the queue: void packet_queue_init(PacketQueue *q) { memset(q, 0, sizeof(PacketQueue)); q->mutex = SDL_CreateMutex(); q->cond = SDL_CreateCond(); } Then we will make a function to put stuff in our queue: 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; } SDL_LockMutex() locks the mutex in the queue so we can add something to it,and then SDL_CondSignal() sends a signal to our get function (if it iswaiting) through our condition variable to tell it that there is data and itcan proceed, then unlocks the mutex to let it go. Here's the corresponding get function. Notice how SDL_CondWait() makes thefunction **block** (i.e. pause until we get data) if we tell it to. 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); ret = 1; break; } else if (!block) { ret = 0; break; } else { SDL_CondWait(q->cond, q->mutex);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -