SDL音频播放

时间:2022-09-26 23:50:59

gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lz -lm \
`sdl-config --cflags --libs`

AUDIO名词解释:
samples:采样,通过PCM来采样,通常样本16bit,PCM的采样精度从14-bit发展到16-bit、18-bit、20-bit直到24-bit
Samples rate:采样率,22.05KHz and 44.1KHz,每秒从连续信号中提取并组成离散信号的采样个数
位速:采样率*样本bit*通道数,CD上未经压缩的音频位速是1411.2 kbit/s(16 位/采样点 × 44100 采样点/秒 × 2 通道)

pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO , 在for循环找找到audiostream的索引i
AVCodecContext *aCodecCtx=pFormatCtx->streams[audioStream]->codec; 得到音频编码的信息

SDL_AudioSpec  wanted_spec,spec;
wanted_spec.freq = aCodecCtx->sample_rate;   //采样率
wanted_spec.format = AUDIO_S16SYS;
//告诉SDL使用什么格式,S指代signed,16为样本16bit,SYS指代大小端由系统决定
wanted_spec.channels = aCodecCtx->channels; //有多少个通道
wanted_spec.silence = 0; //silence值,由于为signed,故为0
wanted_spec.samples =1024; //缓存大小
wanted_spec.callback = audio_callback; //音频的回调函数

wanted_spec.userdata = aCodecCtx; //给回调函数处理的数据
SDL_OpenAudio(&wanted_spec, &spec)
返回-1则打开失败,spec为NULL则以wanted_spec指定的方式播放,若spec不为NULL,则使用根据硬件改变的spec指定的方式播放,而wanted_spec可以删除

VCodec         *aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
avcodec_open(aCodecCtx, aCodec);
找到解码器,并进行解码

typedef struct PacketQueue {
AVPacketList *first_pkt, *last_pkt;
int nb_packets;        //为包的总数
int size;    //为所有包的大小
SDL_mutex *mutex; //互斥锁
SDL_cond *cond; //条件变量
} PacketQueue;
我们自己创建的用于构建Packet队列的数据结构

AVPacketList
A simple linked list for packets.
AVPacket pkt
AVPacketList * next

void packet_queue_init(PacketQueue *q)
{

memset(q,0,sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond=SDL_CreateCond();
}

对PacketQueue数据结构进行初始化

用于给PacketQueue数据结构中填入包的函数
int    packet_queue_put(PacketQueue    *q,AVPacket    *pkt)
{
    AVPacketList    *pkt1;
    if(av_dup_packet(pkt)<0)
    {
        return    -1;    //检查是否为NULL,为NULL则自己填充,否则返回-1
    }
    pkt1    =    av_malloc(sizeof(AVPacketList));//给AVPacketList分配空间

if (!pkt1)
        return -1;
    pkt1->pkt = *pkt;
    pkt1->next = NULL;
    SDL_LockMutex(q->mutex); //对PacketQueue进行操作,先锁定互斥变量
    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;
}

接收数据
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
    AVPacketList    *pkt1;
    int    ret;
    SDL_LockMutex(q->mutex);//锁定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);
        }
    }
    SDL_UnlockMutex(q->mutex);
    return ret;
}
SDL_CondWait先检测是否满足条件,若不满足,解锁mutex,wait,直至被SDL_CondSignal()函数或者SDL_CondBroadcast()函数通知,则锁定mutex并返回

void SDL_PauseAudio(int pause_on)
控制播放与暂停,当pause_on为0时,播放数据,若数据未准备,则播放静音

播放的回调函数,格式必须为void callback(void *userdata, Uint8 *stream, int len),这里的userdata就是我们给到SDL的指针,stream是我们要把声音数据写入的缓冲区指针,len是缓冲区的大小。
void audio_callback(void *userdata, Uint8 *stream, int len) {

struct    mydata    *data=(struct    mydata*)userdata;
AVCodecContext *aCodecCtx = (AVCodecContext *)data->pFormatCtx;
PacketQueue *audioq=data->audioq;
int len1, audio_size;
//静态的数据为了可以多次调用回调函数,而每次不一定处理完了数据
static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
//audio_buf的大小为1.5倍的声音帧的大小以便于有一个比较好的缓冲
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_size = audio_decode_frame(aCodecCtx, audio_buf,
                                      sizeof(audio_buf));
      if(audio_size < 0) {
   
    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;
}
}

对音频数据进行解码,被解码的数据存在audio_buf中,buf_size告诉函数audio_buf缓冲区多大,返回值为解码的数据数量,结束时返回-1,否则返回被解码的bytes数。
int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
                       int buf_size,PacketQueue *audioq) {

static AVPacket pkt;
static uint8_t *audio_pkt_data = NULL;
static int audio_pkt_size = 0;
int len1, data_size;
for(;;) {
    while(audio_pkt_size > 0) {    //该循环从末尾开始
      data_size = buf_size;    //avcodec_decode_audio2必须先指定输出缓冲大小
      len1 = avcodec_decode_audio2(aCodecCtx, (int16_t *)audio_buf, &data_size, audio_pkt_data, audio_pkt_size);

//对数据进行解码
      if(len1 < 0) {
   
    audio_pkt_size = 0;
    break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1; //静态数据,可能包里面有多个帧,故下次调用继续
      if(data_size <= 0) {
   
    continue;
      }
     
      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;
}
}

int avcodec_decode_audio2(AVCodecContext *avctx, int16_t *samples, int *frame_size_ptr, uint8_t *buf, int buf_size)
输出为samples,如果没有可以解码的frame_size_ptr返回0,否则其为被解码的大小。你必须分配frame_size_ptr为samples的缓存大小在你调用该函数时。
返回负数为错误,返回解码的字节数或0指示没有被解码的。
The input buffer must be FF_INPUT_BUFFER_PADDING_SIZE larger than the actual read bytes because some optimized bitstream readers read 32 or 64 bits at once and could read over the end. The end of the input buffer buf should be set to 0 to ensure that no overreading happens for damaged MPEG streams.
Note:You might have to align the input buffer buf and output buffer samples. The alignment requirements depend on the CPU: on some CPUs it isn't necessary at all, on others it won't work at all if not aligned and on others it will work but it will have an impact on performance. In practice, the bitstream should have 4 byte alignment at minimum and all sample data should be 16 byte aligned unless the CPU doesn't need it (AltiVec and SSE do). If the linesize is not a multiple of 16 then there's no sense in aligning the start of the buffer to 16.

SDL音频播放的更多相关文章

  1. FFMPEG &plus; SDL音频播放分析

    目录 [hide] 1 抽象流程: 2 关键实现: 2.1 main()函数 2.2 decode_thread()读取文件信息和音频包 2.3 stream_component_open():设置音 ...

  2. 最简单的基于FFMPEG&plus;SDL的音频播放器 ver2 (采用SDL2&period;0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  3. 最简单的基于FFMPEG&plus;SDL的音频播放器 ver2 (採用SDL2&period;0)

    ===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...

  4. 最简单的视音频播放示例9:SDL2播放PCM

    本文记录SDL播放音频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  5. 最简单的视音频播放示例7:SDL2播放RGB&sol;YUV

    本文记录SDL播放视频的技术.在这里使用的版本是SDL2.实际上SDL本身并不提供视音频播放的功能,它只是封装了视音频播放的底层API.在Windows平台下,SDL封装了Direct3D这类的API ...

  6. ffmpeg音频播放代码示例-avcodec&lowbar;decode&lowbar;audio4

    一.概述 最近在学习ffmpeg解码的内容,参考了官方的教程http://dranger.com/ffmpeg/tutorial03.html,结果发现这个音频解码的教程有点问题.参考了各种博客,并同 ...

  7. FFmpeg简易播放器的实现-音频播放

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10068490.html 基于FFmpeg和SDL实现的简易视频播放器,主要分为读取视频文 ...

  8. 最简单的视音频播放演示样例7:SDL2播放RGB&sol;YUV

    ===================================================== 最简单的视音频播放演示样例系列文章列表: 最简单的视音频播放演示样例1:总述 最简单的视音频 ...

  9. 视频和音频播放的演示最简单的例子9:SDL2广播PCM

    ===================================================== 最简单的视频和音频播放的演示样品系列列表: 最简单的视音频播放演示样例1:总述 最简单的视音 ...

随机推荐

  1. Emacs学习心得之 基础配置

    作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Emacs学习心得之 基础配置 1.前言2.基础配置 一.前言 本篇博文记录了Emacs的一 ...

  2. GBDT(MART) 迭代决策树入门教程 &vert; 简介

    GBDT(MART) 迭代决策树入门教程 | 简介  http://blog.csdn.net/w28971023/article/details/8240756

  3. javascript中this的学习总结

    在开发中,this多使用在function函数中,也正是由于调用function的对象的不同,才导致了this的指向不同.需要明白(1).function也是对象:(2).function执行时是在某 ...

  4. WebUI框架

    Telerik Kendo UI http://demos.telerik.com/kendo-ui/window/right-to-left-support

  5. MySQL数据库基本用法-聚合-分组

    聚合 为了快速得到统计数据,提供了5个聚合函数 count(*)表示计算总行数,括号中写星与列名,结果是相同的 查询学生总数 select count(*) from students; max(列) ...

  6. Linux下进程&sol;程序网络带宽占用情况查看工具 -- NetHogs

    http://www.vpser.net/manage/nethogs.html   来自.  最后略有修改 之前VPS侦探曾经介绍过流量带宽相关的工具如:iftop.vnstat,这几个都是统计和监 ...

  7. JavaScript 第十章总结&colon;first class functions

    前言 这一章的内容是 advanced knowledge and use of functions. 讲了关于 function 的使用的一些特殊的方面. function expression 的 ...

  8. sqlserver判断字段是否存在更改字段

    use naire go if COL_LENGTH('options','optionsGroup') is null begin--options为表名,optionsGroup为列名 alter ...

  9. make ;makefile; cmake; qmake的区分

    1. make 是用来执行Makefile的.2. Makefile是类unix环境下(比如Linux)的类似于批处理的"脚本"文件.其基本语法是: 目标+依赖+命令,只有在目标文 ...

  10. Grafana&plus;Prometheus监控

    添加模板一定要看说明以及依赖 监控redis https://blog.52itstyle.com/archives/2049/ http://www.cnblogs.com/sfnz/p/65669 ...