6.基于FFMPEG+SDL2播放音频

2024-05-27 09:38
文章标签 音频 ffmpeg 播放 sdl2

本文主要是介绍6.基于FFMPEG+SDL2播放音频,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

参考资料:
1.雷博博客
2. An ffmpeg and SDL Tutorial

前面了解了FFMPEG解码MP3文件为PCM,以及将PCM通过SDL2进行播放,下面就是将这两者进行结合,使之能够边解码边播放。。。。

  • 一、综述
  • 二、代码1(基础代码)
  • 三、代码2(增加链表队列)
    • 1)链表结构
    • 2)放数据到队列中
    • 3)从队列中取数据
    • 4)回调函数
    • 5)解码数据

一、综述

总共有2份代码,第一份是参考雷博的代码,第二份是在雷博的代码基础上进行修改,增加了链表队列控制。

二、代码1(基础代码)

关于FFMPEG解码PCM以及SDL播放音频相关知识在前两篇文章中已经详细描述了,所以不再在这里赘述,参考雷博的代码,将前面两篇文章中的代码进行综合,即可正常解码并播放音频。

大致流程为:
初始化复用器和解复用器—>获取输入文件的一些信息—->查找解码器并打开—>初始化SDL—>播放音频—->读出音频数据并解码—>等待SDL读取音频—>SDL回调读取音频数据—>结束。

其中,比较重要的便是回调函数,当设备需要音频数据时候便会调用此回调函数获取音频数据,因此在这个函数中,我们需要将解码后的音频数据赋值给stream。

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif//#define debug_msg(fmt, args...) printf("--->[%s,%d]  " fmt "\n\n", __FUNCTION__, __LINE__, ##args)#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  //48000 * (32/8)unsigned int audioLen = 0;
unsigned char *audioChunk = NULL;
unsigned char *audioPos = NULL;void fill_audio(void * udata, Uint8 * stream, int len)
{SDL_memset(stream, 0, len);if (audioLen == 0)return;len = (len>audioLen?audioLen:len);SDL_MixAudio(stream,audioPos,len,SDL_MIX_MAXVOLUME);audioPos += len;audioLen -= len;
}int test_audio_2_play()
{AVFormatContext *pFortCtx = NULL;AVCodecContext *pCodecCtx = NULL;AVCodec *pCodec = NULL;AVPacket *pPkt = NULL;AVFrame*pFrame = NULL;struct SwrContext *pSwrCtx = NULL;SDL_AudioSpec wantSpec;//FILE* outFile = fopen("output.pcm", "wb");char inFile[] = "skycity1.mp3";int ret = -1;int audioIndex = -1;int i = 0;int got_picture = -1;uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; //声音格式int out_sample_rate=44100;   //采样率int out_nb_samples = -1;   int out_channels = -1;        //通道数int out_buffer_size = -1;   //输出buffunsigned char *outBuff = NULL;uint64_t in_chn_layout = -1;  //通道布局 struct SwrContext *au_convert_ctx;av_register_all();pFortCtx = avformat_alloc_context();  if (avformat_open_input(&pFortCtx, inFile, NULL, NULL) != 0)   //open input file and read data into buf{printf("avformat_open_input error!\n");ret = -1;goto ERR_1;}if (avformat_find_stream_info(pFortCtx, NULL) < 0)   //find stream some info{printf("avformat_find_stream_info error!\n");ret = -1;goto ERR_1;}/* find audio index */for (i = 0; i < pFortCtx->nb_streams; i++){if (pFortCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){audioIndex = i;break;}}if (-1 == audioIndex){printf("can not find audio index!\n");ret = -1;goto ERR_1;}printf("------>audioIndex is %d\n", audioIndex);pCodecCtx = pFortCtx->streams[audioIndex]->codec;pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if (NULL == pCodec){printf("can not find decoder!\n");ret = -1;goto ERR_1;}if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){printf("Could not open codec.\n");ret = -1;goto ERR_1;}if (NULL == (pPkt = (AVPacket *)av_malloc(sizeof(AVPacket)))){printf("AV malloc failure.\n");ret = -1;goto ERR_2;}//out parameterout_nb_samples = pCodecCtx->frame_size;out_channels = av_get_channel_layout_nb_channels(out_chn_layout);out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples,  out_sample_fmt, 1);outBuff = (unsigned char *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //双声道printf("-------->out_buffer_size is %d\n",out_buffer_size);in_chn_layout = av_get_default_channel_layout(pCodecCtx->channels);pFrame = av_frame_alloc();//SDLwantSpec.freq = out_sample_rate;wantSpec.format = AUDIO_S16SYS;wantSpec.channels = out_channels;wantSpec.silence = 0;wantSpec.samples = out_nb_samples;wantSpec.callback = fill_audio;wantSpec.userdata = pCodecCtx;if (SDL_OpenAudio(&wantSpec, NULL) < 0){printf("can not open SDL!\n");ret = -1;goto ERR_3;}//Swrau_convert_ctx=swr_alloc_set_opts(NULL,out_chn_layout,                                /*out*/out_sample_fmt,                              /*out*/out_sample_rate,                             /*out*/in_chn_layout,                                  /*in*/pCodecCtx->sample_fmt ,               /*in*/pCodecCtx->sample_rate,               /*in*/0, NULL);swr_init(au_convert_ctx);SDL_PauseAudio(0);while(av_read_frame(pFortCtx, pPkt) >= 0){if (pPkt->stream_index == audioIndex){if (avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, pPkt) < 0){printf("Error in decoding audio frame.\n");ret = -1;break;}if (got_picture > 0){swr_convert(au_convert_ctx,&outBuff, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);//fwrite(outBuff, 1, out_buffer_size, outFile); while(audioLen > 0)SDL_Delay(1); audioChunk = (unsigned char *)outBuff;audioPos = audioChunk;audioLen = out_buffer_size;}}av_free_packet(pPkt);}SDL_CloseAudio();SDL_Quit();swr_free(&au_convert_ctx);ERR_3:av_free(outBuff);ERR_2:avcodec_close(pCodecCtx);avformat_close_input(&pFortCtx);ERR_1:avformat_free_context(pFortCtx);//fclose(outFile);return ret;
}int main(int argc, char *argv[])
{//test_audio_2_PCM();test_audio_2_play();return 0;
}

三、代码2(增加链表队列)

在这份代码中,主要是增加了链表队列,并将解码工作放在了回调函数中。
和上面的区别是:
主程序中,每读取一包的数据(av_read_frame)将其放入链表中
在回调函数中,从链表中取出每包的数据并将其解码放入stream中

1)链表结构

链表结构如下:

typedef struct PacketQueue {AVPacketList *first_pkt, *last_pkt;int nb_packets;int size;SDL_mutex *mutex;SDL_cond *cond;
} PacketQueue;

其中,first_pkt和last_pkt为两个主要的链表结点,first_pkt用于指向第一个链表结点,当我们取数据的时候,总是取出第一个结点的数据,并将第一个结点的next重新复制给first_pkt,用于下次取数据时使用,last_pkt为最后一个结点,当放数据时候,会将新的数据结点地址赋值给last_pkt的next,并将last_pkt重新指向最后一个结点,依此反复,不断将新的数据结点添加到链表的最后。
其原型为:

typedef struct AVPacketList {AVPacket pkt;struct AVPacketList *next;
} AVPacketList;

nb_packets为当前的总packet数目
size 为当前所有packet中数据的大小
mutex 为互斥锁
cond 为条件变量

因为主线程会不断向链表中放入读取到的数据以及回调函数中会不断从链表中读取数据,所以需要有互斥锁以及条件变量来进行同步操作。

2)放数据到队列中

主函数中将读取到的数据放到队列中,函数原型如下:

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {AVPacketList *pkt1;if(av_dup_packet(pkt) < 0) {return -1;}pkt1 = (AVPacketList *)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;elseq->last_pkt->next = pkt1;  //将当前链表的最后一个结点的next指向pkt1q->last_pkt = pkt1; //将last_pkt指向最后一个结点,即pkt1q->nb_packets++;q->size += pkt1->pkt.size;SDL_CondSignal(q->cond);SDL_UnlockMutex(q->mutex);return 0;
}

注意,在进行链表操作的时候,需要加锁解锁,防止其他地方同时进行操作。

3)从队列中取数据

从链表队列中取出结点数据,如下:

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; //即q->first_pkt = q->first_pkt->next 将第一个结点指向的下一个结点设置为first_pkt,可以理解为取出当前第一个结点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);}}SDL_UnlockMutex(q->mutex);return ret;
}

4)回调函数

void audio_callback(void *userdata, Uint8 *stream, int len) {AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;int len1, audio_size;static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];static unsigned int audio_buf_size = 0;static unsigned int audio_buf_index = 0;while(len > 0) {   if(audio_buf_index >= audio_buf_size)  //表示当前audio_buf中已经没有数据,需要解码数据了{/* We have already sent all our data; get more */audio_size = audio_decode_frame(aCodecCtx, audio_buf,sizeof(audio_buf));if(audio_size < 0) {/* If error, output silence */audio_buf_size = 1024;memset(audio_buf, 0, audio_buf_size);} else {audio_buf_size = audio_size;}audio_buf_index = 0;}len1 = audio_buf_size - audio_buf_index;if(len1 > len)len1 = len;memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);len -= len1;stream += len1;audio_buf_index += len1;}
} 

在上面代码中有三个static 变量:
audio_buf 、audio_buf_size、 audio_buf_index
audio_buf中存放的为解码后的数据
audio_buf_size 为当前audio_buf中数据的长度
audio_buf_index为当前指向audio_buf的index

之所以设置为static,是因为当调用此回调函数时候,SDL期望的数据长度为len,但是可能我们调用audio_decode_frame返回的数据总长度大于len,所以将长度为len的数据赋值给stream,当下一次回调进来的时候,先根据audio_buf_size和audio_buf_index的大小来进行判断是否需要再解码新的数据,如果还有剩余数据,则继续将audio_buf中的剩余数据赋值给stream,此时如果剩余数据长度小于len,则循环解码新的数据进行赋值。

5)解码数据

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {static AVPacket pkt;static uint8_t *audio_pkt_data = NULL;static int audio_pkt_size = 0;static AVFrame frame;int len1, data_size = 0;for(;;) {while(audio_pkt_size > 0) {int got_frame = 0;len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);if(len1 < 0) {/* if error, skip frame */audio_pkt_size = 0;break;}audio_pkt_data += len1;audio_pkt_size -= len1;data_size = 0;if(got_frame) {swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);data_size = out_buffer_size;/*data_size = av_samples_get_buffer_size(NULL, aCodecCtx->channels,frame.nb_samples,aCodecCtx->sample_fmt,1);assert(data_size <= buf_size);memcpy(audio_buf, frame.data[0], data_size);*/}if(data_size <= 0) {/* No data yet, get more frames */continue;}/* We have data, return it and come back for more later */return data_size;}if(pkt.data)av_free_packet(&pkt);if(quit) {return -1;}if(packet_queue_get(&audioq, &pkt, 1) < 0) {return -1;}audio_pkt_data = pkt.data;audio_pkt_size = pkt.size;}
}

在这个函数中,仍然有几个static变量,pkt/audio_pkt_data/audio_pkt_size/frame.

首先我们先了解一下解码函数avcodec_decode_audio4中的一些描述:

  • Some decoders may support multiple frames in a single AVPacket. Such
  • decoders would then just decode the first frame and the return value would be
  • less than the packet size. In this case, avcodec_decode_audio4 has to be
  • called again with an AVPacket containing the remaining data in order to
  • decode the second frame, etc… Even if no frames are returned, the packet
  • needs to be fed to the decoder with remaining data until it is completely
  • consumed or an error occurs.

上面大意是,一个AVPacket中可能会有多帧音频数据,但是avcodec_decode_audio4每次只解码一帧的数据,因此需要我们多次调用此接口来解码AVPacket中的数据。

根据以上,所以当audio_decode_frame解码一帧后就返回,当下次进来此函数时候,会继续将pkt里面的剩余数据进行解码,当将pkt里面的剩余数据解码万之后,在下次进来的时候会重新从队列里面读取数据,audio_pkt_size和audio_pkt_data则是与此过程相关。

同时,解码后的数据格式可能不是我们想要的或者SDL2无法播放,则需要调用SwrContext进行重采样,如下:

swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);

以上便是几个重要的函数解析,下面是完整的代码:

//采用队列的方式
//linux下编译命令如下:
//gcc test_2_pcm.c -o test -I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil -lswresample//windows下可以直接套用雷博的工程#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>#define __STDC_CONSTANT_MACROS#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "SDL2/SDL.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
};
#endif
#endif//#define debug_msg(fmt, args...) printf("--->[%s,%d]  " fmt "\n\n", __FUNCTION__, __LINE__, ##args)#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio  //48000 * (32/8)
#define SDL_AUDIO_BUFFER_SIZE 1024struct SwrContext *au_convert_ctx;
int out_buffer_size = -1;   //输出buff长度typedef struct PacketQueue {AVPacketList *first_pkt, *last_pkt;int nb_packets;int size;SDL_mutex *mutex;SDL_cond *cond;
} PacketQueue;PacketQueue audioq;void packet_queue_init(PacketQueue *q) {memset(q, 0, sizeof(PacketQueue));q->first_pkt = NULL;q->last_pkt = NULL;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 = (AVPacketList *)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;elseq->last_pkt->next = pkt1;  //将当前链表的最后一个结点的next指向pkt1q->last_pkt = pkt1; //将last_pkt指向最后一个结点,即pkt1q->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; //即q->first_pkt = q->first_pkt->next 将第一个结点指向的下一个结点设置为first_pkt,可以理解为取出当前第一个结点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);}}SDL_UnlockMutex(q->mutex);return ret;
}int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {static AVPacket pkt;static uint8_t *audio_pkt_data = NULL;static int audio_pkt_size = 0;static AVFrame frame;int len1, data_size = 0;for(;;) {while(audio_pkt_size > 0) {int got_frame = 0;len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);if(len1 < 0) {/* if error, skip frame */audio_pkt_size = 0;break;}audio_pkt_data += len1;audio_pkt_size -= len1;data_size = 0;if(got_frame) {swr_convert(au_convert_ctx,&audio_buf, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)frame.data , frame.nb_samples);data_size = out_buffer_size;/*data_size = av_samples_get_buffer_size(NULL, aCodecCtx->channels,frame.nb_samples,aCodecCtx->sample_fmt,1);assert(data_size <= buf_size);memcpy(audio_buf, frame.data[0], data_size);*/}if(data_size <= 0) {/* No data yet, get more frames */continue;}/* We have data, return it and come back for more later */return data_size;}if(pkt.data)av_free_packet(&pkt);if(quit) {return -1;}if(packet_queue_get(&audioq, &pkt, 1) < 0) {return -1;}audio_pkt_data = pkt.data;audio_pkt_size = pkt.size;}
}void audio_callback(void *userdata, Uint8 *stream, int len) {AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;int len1, audio_size;static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];static unsigned int audio_buf_size = 0;static unsigned int audio_buf_index = 0;while(len > 0) {   if(audio_buf_index >= audio_buf_size) //表示当前audio_buf中已经没有数据,需要解码数据了{/* We have already sent all our data; get more */audio_size = audio_decode_frame(aCodecCtx, audio_buf,sizeof(audio_buf));if(audio_size < 0) {/* If error, output silence */audio_buf_size = 1024;memset(audio_buf, 0, audio_buf_size);} else {audio_buf_size = audio_size;}audio_buf_index = 0;}len1 = audio_buf_size - audio_buf_index;if(len1 > len)len1 = len;memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);len -= len1;stream += len1;audio_buf_index += len1;}
} 
int test_audio_2_play()
{AVFormatContext *pFortCtx = NULL;AVCodecContext *pCodecCtx = NULL;AVCodec *pCodec = NULL;AVPacket *pPkt = NULL;AVFrame*pFrame = NULL;struct SwrContext *pSwrCtx = NULL;SDL_AudioSpec wantSpec;//FILE* outFile = fopen("output.pcm", "wb");char inFile[] = "skycity1.mp3";int ret = -1;int audioIndex = -1;int i = 0;int got_picture = -1;uint64_t out_chn_layout = AV_CH_LAYOUT_STEREO;  //通道布局 输出双声道enum AVSampleFormat out_sample_fmt=AV_SAMPLE_FMT_S16; //声音格式int out_sample_rate=44100;   //采样率int out_nb_samples = -1;   int out_channels = -1;        //通道数unsigned char *outBuff = NULL;uint64_t in_chn_layout = -1;  //通道布局 av_register_all();pFortCtx = avformat_alloc_context();  if (avformat_open_input(&pFortCtx, inFile, NULL, NULL) != 0)   //open input file and read data into buf{printf("avformat_open_input error!\n");ret = -1;goto ERR_1;}if (avformat_find_stream_info(pFortCtx, NULL) < 0)   //find stream some info{printf("avformat_find_stream_info error!\n");ret = -1;goto ERR_1;}/* find audio index */for (i = 0; i < pFortCtx->nb_streams; i++){if (pFortCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){audioIndex = i;break;}}if (-1 == audioIndex){printf("can not find audio index!\n");ret = -1;goto ERR_1;}printf("------>audioIndex is %d\n", audioIndex);pCodecCtx = pFortCtx->streams[audioIndex]->codec;pCodec = avcodec_find_decoder(pCodecCtx->codec_id);if (NULL == pCodec){printf("can not find decoder!\n");ret = -1;goto ERR_1;}if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){printf("Could not open codec.\n");ret = -1;goto ERR_1;}if (NULL == (pPkt = (AVPacket *)av_malloc(sizeof(AVPacket)))){printf("AV malloc failure.\n");ret = -1;goto ERR_2;}//out parameterout_nb_samples = pCodecCtx->frame_size;out_channels = av_get_channel_layout_nb_channels(out_chn_layout);out_buffer_size = av_samples_get_buffer_size(NULL, out_channels, out_nb_samples,  out_sample_fmt, 1);outBuff = (unsigned char *)av_malloc(MAX_AUDIO_FRAME_SIZE*2); //双声道printf("-------->out_buffer_size is %d\n",out_buffer_size);in_chn_layout = av_get_default_channel_layout(pCodecCtx->channels);pFrame = av_frame_alloc();//SDLwantSpec.freq = out_sample_rate;wantSpec.format = AUDIO_S16SYS;wantSpec.channels = out_channels;wantSpec.silence = 0;wantSpec.samples = out_nb_samples;wantSpec.callback = audio_callback;wantSpec.userdata = pCodecCtx;if (SDL_OpenAudio(&wantSpec, NULL) < 0){printf("can not open SDL!\n");ret = -1;goto ERR_3;}//Swrau_convert_ctx=swr_alloc_set_opts(NULL,out_chn_layout,                                /*out*/out_sample_fmt,                              /*out*/out_sample_rate,                             /*out*/in_chn_layout,                                  /*in*/pCodecCtx->sample_fmt ,               /*in*/pCodecCtx->sample_rate,               /*in*/0, NULL);swr_init(au_convert_ctx);SDL_PauseAudio(0);while(av_read_frame(pFortCtx, pPkt) >= 0){if (pPkt->stream_index == audioIndex){#if 0if (avcodec_decode_audio4(pCodecCtx, pFrame, &got_picture, pPkt) < 0){printf("Error in decoding audio frame.\n");ret = -1;break;}if (got_picture > 0){swr_convert(au_convert_ctx,&outBuff, MAX_AUDIO_FRAME_SIZE,(const uint8_t **)pFrame->data , pFrame->nb_samples);//fwrite(outBuff, 1, out_buffer_size, outFile); while(audioLen > 0)SDL_Delay(1); audioChunk = (unsigned char *)outBuff;audioPos = audioChunk;audioLen = out_buffer_size;}#elsepacket_queue_put(&audioq, pPkt);#endif}elseav_free_packet(pPkt);}sleep(10);SDL_CloseAudio();SDL_Quit();swr_free(&au_convert_ctx);ERR_3:av_free(outBuff);ERR_2:avcodec_close(pCodecCtx);avformat_close_input(&pFortCtx);ERR_1:avformat_free_context(pFortCtx);//fclose(outFile);return ret;
}int main(int argc, char *argv[])
{packet_queue_init(&audioq);//test_audio_2_PCM();test_audio_2_play();return 0;
}

最后是一个完整的工程,工程“无耻” ( ̄▽ ̄)~*的使用了雷博的工程,在visual studio 2010上跑通了,直接将代码写入工程编译运行。
基于FFMPEG+SDL2播放音频

这篇关于6.基于FFMPEG+SDL2播放音频的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007108

相关文章

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

ffmpeg面向对象-待定

1.常用对象 rtsp拉流第一步都是avformat_open_input,其入参可以看下怎么用: AVFormatContext *fmt_ctx = NULL;result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); 其中fmt_ctx 如何分配内存的?如下 int avformat_open_input(

一款支持同一个屏幕界面同时播放多个视频的视频播放软件

GridPlayer 是一款基于 VLC 的免费开源跨平台多视频同步播放工具,支持在一块屏幕上同时播放多个视频。其主要功能包括: 多视频播放:用户可以在一个窗口中同时播放任意数量的视频,数量仅受硬件性能限制。支持多种格式和流媒体:GridPlayer 支持所有由 VLC 支持的视频格式以及流媒体 URL(如 m3u8 链接)。自定义网格布局:用户可以配置播放器的网格布局,以适应不同的观看需求。硬

FFmpeg系列-视频解码后保存帧图片为ppm

在正常开发中遇到花屏时怎么处理呢?可以把解码后的数据直接保存成帧图片保存起来,然后直接看图片有没有花屏来排除是否是显示的问题,如果花屏,则代表显示无问题,如果图片中没有花屏,则可以往显示的方向去排查了。 void saveFrame(AVFrame* pFrame, int width, int height, int iFrame){FILE *pFile;char szFilename[

UniApp实现漂亮的音乐歌词滚动播放效果

在现代的音乐播放应用中,歌词的展示和滚动播放已经成为了一个非常常见的功能。今天,我们将通过UniApp来实现一个漂亮的歌词滚动播放功能。我们将使用UniApp提供的组件和API来完成这个任务。 页面结构 在页面的模板部分,我们需要创建一个音频播放器和歌词展示区域。使用<scroll-view>组件来实现歌词的滚动效果。 <template><view class="audio-co

【IPV6从入门到起飞】4-RTMP推流,ffmpeg拉流,纯HTML网页HLS实时直播

【IPV6从入门到起飞】4-RTMP推流,ffmpeg拉流,纯HTML网页HLS实时直播 1 背景2 搭建rtmp服务器2.1 nginx方案搭建2.1.1 windows 配置2.1.2 linux 配置 2.2 Docker方案搭建2.2.1 docker 下载2.2.2 宝塔软件商店下载 3 rtmp推流3.1 EV录屏推流3.2 OBS Studio推流 4 ffmpeg拉流转格式

Vue 中实现视频播放的艺术

随着前端技术的飞速发展,视频播放在 Web 应用中已经成为了一个不可或缺的功能。从社交媒体平台到教育网站,再到在线购物平台,视频元素无处不在。而 Vue.js 作为当今最流行的前端框架之一,在实现视频播放时提供了很多强大的工具和技巧。在这篇博客中,我们将深入探讨如何使用 Vue.js 实现视频播放功能,不仅仅是简单地嵌入视频,还要添加一些高级功能,如自定义播放器控件、播放进度条、视频切换、事件处理