• 音视频项目—基于FFmpeg和SDL的音视频播放器解析(十五)


    介绍

    在本系列,我打算花大篇幅讲解我的 gitee 项目音视频播放器,在这个项目,您可以学到音视频解封装,解码,SDL渲染相关的知识。您对源代码感兴趣的话,请查看基于FFmpeg和SDL的音视频播放器

    如果您不理解本文,可参考我的前一篇文章音视频项目—基于FFmpeg和SDL的音视频播放器解析(十四)

    解析

    我们这篇文章接着解析 audiooutput 剩余的函数。

    有一个很关键的函数,fill_audio_pcm

    1. FILE* dump_pcm = nullptr;
    2. void fill_audio_pcm(void* udata, uint8_t* stream, int len){
    3. AudioOutput* is = (AudioOutput*)udata;
    4. int len1 = 0;
    5. int audio_size = 0;
    6. if(!dump_pcm){
    7. dump_pcm = fopen("dump.pcm", "wb");
    8. }
    9. while (len > 0)
    10. {
    11. if(is->audio_buf_index == is->audio_buf_size){
    12. is->audio_buf_index = 0;
    13. AVFrame* frame = is->frame_queue->Pop(10);
    14. if(frame){
    15. is->pts = frame->pts;
    16. if(frame->format != is->dst_tgt.fmt
    17. || frame->sample_rate != is->dst_tgt.freq
    18. || frame->channel_layout != is->dst_tgt.channel_layout
    19. && !is->swr_ctx){
    20. is->swr_ctx = swr_alloc_set_opts(NULL,
    21. is->dst_tgt.channel_layout,
    22. (enum AVSampleFormat)is->dst_tgt.fmt,
    23. is->dst_tgt.freq,
    24. frame->channel_layout,
    25. (enum AVSampleFormat)frame->format,
    26. frame->sample_rate,
    27. 0, NULL);
    28. if(!is->swr_ctx || swr_init(is->swr_ctx) < 0){
    29. swr_free((SwrContext**)(&is->swr_ctx));
    30. return;
    31. }
    32. }
    33. if(is->swr_ctx){
    34. const uint8_t** in = (const uint8_t**) frame->extended_data;
    35. uint8_t** out = &is->audio_buf1;
    36. int out_samples = frame->nb_samples * is->dst_tgt.freq / frame->sample_rate + 256;
    37. int out_bytes = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, out_samples, is->dst_tgt.fmt, 0);
    38. if(out_bytes < 0){
    39. return;
    40. }
    41. av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_bytes);
    42. int len2 = swr_convert(is->swr_ctx, out, out_samples, in, frame->nb_samples);
    43. if(len2 < 0){
    44. return;
    45. }
    46. is->audio_buf = is->audio_buf1;
    47. is->audio_buf_size = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, len2, is->dst_tgt.fmt, 1);
    48. }else {
    49. audio_size = av_samples_get_buffer_size(NULL, is->dst_tgt.channels, frame->nb_samples, (enum AVSampleFormat) frame->format, 1);
    50. av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, audio_size);
    51. is->audio_buf = is->audio_buf1;
    52. is->audio_buf_size = audio_size;
    53. memcpy(is->audio_buf, frame->data[0], audio_size);
    54. }
    55. av_frame_free(&frame);
    56. }else {
    57. is->audio_buf = nullptr;
    58. is->audio_buf_size = 512;
    59. }
    60. }
    61. len1 = is->audio_buf_size - is->audio_buf_index;
    62. if(len1 > len){
    63. len1 = len;
    64. }
    65. if(!is->audio_buf){
    66. memset(stream, 0, len1);
    67. }else {
    68. memcpy(stream, is->audio_buf + is->audio_buf_index, len1);
    69. fwrite((uint8_t*)is->audio_buf + is->audio_buf_index, 1, len1, dump_pcm);
    70. fflush(dump_pcm);
    71. }
    72. len -= len1;
    73. stream += len1;
    74. is->audio_buf_index += len1;
    75. }
    76. if(is->pts != AV_NOPTS_VALUE){
    77. double pts = is->pts * av_q2d(is->time_base);
    78. is->avsync->SetClock(pts);
    79. }
    80. }

    这个函数有将近 80 行代码,负责将 pcm 数据填入音频。这个函数是用在 Init 函数上的,用于给 SDL_AudioSpec 的变量的 callback 赋值。

    wanted_spec.callback = fill_audio_pcm;

    接下来,我们逐条解析这个函数。

    首先看前五行代码

    1. AudioOutput* is = (AudioOutput*)udata;
    2. int len1 = 0;
    3. int audio_size = 0;
    4. if(!dump_pcm){
    5. dump_pcm = fopen("dump.pcm", "wb");
    6. }

    就是一些正常的赋值操作,如果文件不存在,则打开一个名为 “dump.pcm” 的文件,设为二进制可写。

    1. while (len > 0)
    2. {
    3. if(is->audio_buf_index == is->audio_buf_size){
    4. is->audio_buf_index = 0;
    5. AVFrame* frame = is->frame_queue->Pop(10);

    在长度 len 大于 0 的情况下,如果两个变量相等,则将 index 设置为 0,取出帧队列的头部数据,10 是 Pop 里的参数,关于条件变量的,这里不深究。

    1. if(frame){
    2. is->pts = frame->pts;
    3. if(frame->format != is->dst_tgt.fmt
    4. || frame->sample_rate != is->dst_tgt.freq
    5. || frame->channel_layout != is->dst_tgt.channel_layout
    6. && !is->swr_ctx){

    然后,在帧数据存在的情况下,将 pts(显示时间戳)赋值,然后开始条件判断,满足这些条件后执行。

    好了,这篇文章先讲 20 行代码,剩余的后几篇文章再讲。

    欲知后事如何,请听下回分解。

  • 相关阅读:
    【前端】js实现队列功能 先进后出 先进先出 等
    一定能用到的简单但实用的五种按钮样式(HTML+CSS步骤详解,含详细注释)
    【牛客-剑指offer-数据结构篇】JZ25 合并两个排序的链表 三种思路 Java实现
    Day656.数据传输加密问题 -Java业务开发常见错误
    【DSP】【第二篇】了解C6678和创建工程
    《Python进阶系列》十一:集合(set and frozenset)语法汇总
    6.网络编程套接字(上)
    【FreeRTOS】FreeRTOS删除任务vTaskDelete()
    CentOS7搭建FLV和RTMP流媒体服务器
    Java执行cmd或者shell命令,并获取结果
  • 原文地址:https://blog.csdn.net/weixin_60701731/article/details/134513602