Linux下基于ffmpeg音视频解码

1.ffmpeg简介

  FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。
  Fmpeg 是领先的多媒体框架,能够解码、编码、转码、混合、解密、流媒体、过滤和播放人类和机器创造的几乎所有东西。它支持最晦涩的古老格式,直到最尖端的格式。无论它们是由某个标准委员会、社区还是公司设计的。它还具有高度的便携性。

2.ffmpeg八大库介绍

  • avutil工具库
      avutil: 工具库(大部分库都需要这个库的支持),如AVLog日志输出、AVOption (AVClass)选项设置、AVDictionary键值对存储、ParseUtil字符串解析等;
  • avcodec编解码库
      avcodec: 编解码(最重要的库) 。其中AVCodec是存储编解码器信息的结构体,包含了解协议,解封装,解码操作;
      AVCodec结构体中重点参数说明:

const char *name:编解码器的名字,比较短
const char *long_name:编解码器的名字,全称,比较长
enum AVMediaType type:指明了类型,是视频,音频,还是字幕
enum AVCodecID id:ID,音视频流ID信息,枚举类型
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小

  • avformat: 封装格式处理
      AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。

AVIOContext *pb;字节流IO上下文
unsigned int nb_streams;音视频流个数
AVStream **streams;音视频流
char filename[1024];输入输出文件名
int64_t duration;流持续时间,即总时长,单位为us
int bit_rate:比特率(码率)(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据

  • avdevice: 各种设备的输入输出

   可以读取电脑(或其他设备上)的多媒体设备的数据,或者输出到指定的多媒体设备上。

  • avfilter: 滤镜特效处理

   libavfilter提供了一个通用的音视频filter框架。使用avfilter可以对音视频数据做一些效果处理如去色调、模糊、水平翻转、裁剪、加方框、叠加文字等功能。

  • swscale: 视频像素数据格式转换

   libswscale 是 FFmpeg 中完成图像尺寸缩放和像素格式转换的库。用户可以编写程序,调用 libswscale 提供的 API 来进行图像尺寸缩放和像素格式转换。

   libswscale中常用函数:

sws_getContext();初始化一个SwsContext。
sws_scale();处理图像数据。
sws_freeContext();释放一个SwsContext。

  • swresample: 音频采样数据格式转换

  libswresample库功能主要包括高度优化的音频重采样、rematrixing和样本格式转换操作。

  • postproc:后加工

  (同步、时间计算的简单算法)用于后期效果处理;

3.ffmpeg解码流程

  1. 打开文件avformat_open_input;
  2. 寻找解码器avformat_find_stream_info;
  3. 寻找音视频流信息av_find_best_stream;
  4. 寻找音视频解码器avcodec_find_decoder;
  5. 配置音频参数:声道、采样格式、采样率、样本数量、通道个数,进行音频重采样swr_alloc_set_opts(音频格式转码);
  6. 配置视频解码参数:分配视频帧,申请存放图像数据空间,计算一帧空间大小,进行图像转码sws_getContext;
  7. 初始化SDL,实现图像渲染和音频播放;
  8. 读取数据包av_read_frame,实现视频解析和音频解析;

4.ffmpeg解码示例

  • 开发环境

开发平台: ubuntu18.04.6
ffmpeg版本: 4.2.5
SDL版本: 2.0.14

  • 工程示例
#include <stdio.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include <libswresample/swresample.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <stdio.h>
#include <linux/fb.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <poll.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <wchar.h>
#include <pthread.h>
#include <signal.h>#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>#define FILE_NAME "123.flv"
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef enum
{false,true,
}bool;
uint8_t *out_buffer;
#define MAX_AUDIO_FRAME_SIZE 1024*100
static Uint8* audio_chunk;
static unsigned int audio_len=0;
static unsigned char *audio_pos;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁//保存音频数据链表
struct AUDIO_DATA
{unsigned char* audio_buffer;int audio_size;struct AUDIO_DATA *next;
};
//定义一个链表头
struct AUDIO_DATA *list_head=NULL;
struct AUDIO_DATA *List_CreateHead(struct AUDIO_DATA *head);//创建链表头
void List_AddNode(struct AUDIO_DATA *head,unsigned char* audio_buffer,int audio_size);//添加节点
void List_DelNode(struct AUDIO_DATA *head,unsigned char* audio_buffer);//删除节点
int List_GetNodeCnt(struct AUDIO_DATA *head);//遍历
int List_GetNode(struct AUDIO_DATA *head,char *audio_buff,int *audio_size);int file_stat=1;void  AudioCallback(void *userdata, Uint8 * stream,int len)
{SDL_memset(stream, 0,len);if(audio_len<=0){return ;}len=(len>audio_len?audio_len:len);SDL_MixAudio(stream,audio_pos,len,SDL_MIX_MAXVOLUME);audio_pos+=len;audio_len-=len;//printf("len=%d\n",len);
}void *Audio_decode(void *arg)
{int res;int audio_size;char audio_buff[4096*3];while(1){res=List_GetNode(list_head,audio_buff,&audio_size);if(res==0){audio_chunk = audio_buff; //指向音频数据 (PCM data)while(audio_len>0){}//等待数据处理完audio_len =audio_size;//音频长度audio_pos = audio_buff;//当前播放位置}}
}
int main(int argc,char *argv[])
{if(argc!=2){printf("格式:./app 文件名\n");return 0;}char *file_name=argv[1];/*SDL初始化*/SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER);printf("pth:%s\n",avcodec_configuration());/*获取ffmpeg配置信息*//*初始化所有组件*///av_register_all();/*打开文件*/AVCodecContext  *pCodecCtx;//解码器上下文AVFormatContext *ps=NULL;//音视频封装格式结构体信息printf("name:%s\n",file_name);int res=avformat_open_input(&ps,file_name,NULL,NULL);if(res!=0){printf("open err: %d\n",res);return 0;}/*寻找解码信息*/avformat_find_stream_info(ps,NULL);int64_t time=ps->duration;printf("time:%ld s\n",time/1000000);/*打印有关输入或输出格式的详细信息*/av_dump_format(ps,0,file_name,0);/*寻找视频流信息*/int videostream=-1;int audiostream=-1;AVCodec *vcodec;videostream=av_find_best_stream(ps,AVMEDIA_TYPE_VIDEO,-1,-1,NULL, 0);printf("video=%d\n",videostream);/*寻找音频流信息*/audiostream=av_find_best_stream(ps,AVMEDIA_TYPE_AUDIO,-1,-1,NULL, 0);printf("audio=%d\n",audiostream);AVStream *stream;int frame_rate;if(videostream!=-1)//判断是否找到视频流数据{/*寻找视频解码器*/AVStream *stream = ps->streams[videostream];vcodec=avcodec_find_decoder(stream->codecpar->codec_id);if(!vcodec){printf("未找到视频解码器\n");return -1;}/*申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。*/res=avcodec_open2(stream->codec,vcodec,NULL);if(res){printf("打开解码器失败\n");return -1;}frame_rate=stream->avg_frame_rate.num/stream->avg_frame_rate.den;//每秒多少帧printf("fps=%d\n",frame_rate);printf("视频流ID=%#x\n",vcodec->id);//音频流}/*音频流数据处理*/AVCodec *audcodec;AVStream *audstream;SwrContext *swrCtx;//保存重采样数据,即解码的信息uint64_t out_channel_layout;//声道int out_sample_fmt;//采样格式int out_sample_rate;//采样率int out_nb_samples;//样本数量int out_channels;//通道数量uint64_t in_channel_layout;//输入音频声道SDL_AudioSpec desired;//SDL音频格式信息AVFrame *audioframe;//保存音频数据int out_buffer_size;//音频缓冲区大小if(audiostream>=0)//判断是否有音频流{/*寻找音频解码器*/audstream = ps->streams[audiostream];audcodec=avcodec_find_decoder(audstream->codecpar->codec_id);if(!audcodec){printf("audcodec failed\n");return -1;}/*申请音频AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。*/pCodecCtx=audstream->codec;//解码器上下文res=avcodec_open2(audstream->codec,audcodec,NULL);if(res){printf("未找到音频解码器\n");return -1;}printf("音频流ID=%#x\n",audcodec->id);//音频流printf("配置音频参数\n");//输出音频参数out_channel_layout  = AV_CH_LAYOUT_STEREO;  //声道格式out_sample_fmt=AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_S32;//;//采样格式printf("pCodecCtx->sample_rate=%d\n",pCodecCtx->sample_rate);out_sample_rate =pCodecCtx->sample_rate;//采样率,多为44100/*样本数量*/printf("frame_size=%d\n",pCodecCtx->frame_size);if(pCodecCtx->frame_size>0)out_nb_samples=pCodecCtx->frame_size;else if(audcodec->id == AV_CODEC_ID_AAC) out_nb_samples=1024;/*样本数量nb_samples: AAC-1024 MP3-1152  格式大小 */else if(audcodec->id == AV_CODEC_ID_MP3)out_nb_samples=1152;else out_nb_samples=1024;out_channels=av_get_channel_layout_nb_channels(out_channel_layout);//通道个数out_buffer_size=av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,out_sample_fmt,1);//获取缓冲区大小out_buffer=(uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE);memset(out_buffer,0,out_buffer_size);printf("声道格式:%d\n",out_channel_layout);printf("采样格式:%d\n",out_sample_fmt);    printf("样本数量:%d\n",out_nb_samples);   printf("采样率:%d\n",out_sample_rate);printf("通道个数:%d\n",out_channels);printf("缓冲区大小:%d\n",out_buffer_size);//输入音频参数in_channel_layout=av_get_default_channel_layout(pCodecCtx->channels);//输入声道格式swrCtx = swr_alloc();/*对解码数据进行重采样*/swrCtx=swr_alloc_set_opts(swrCtx,out_channel_layout,out_sample_fmt,out_sample_rate,/*输入音频格式*/in_channel_layout,pCodecCtx->sample_fmt,pCodecCtx->sample_rate,/*输出音频格式*/0,NULL);               swr_init(swrCtx);//初始化swrCtxprintf("输入音频格式:%d\n",in_channel_layout);printf("输入采样格式:%d\n",pCodecCtx->sample_fmt);printf("输入采样率:%d\n",pCodecCtx->sample_rate);/*设置音频数据格式*/desired.freq=out_sample_rate;/*采样率*/desired.format=AUDIO_S16SYS;/*无符号16位*/desired.channels=out_channels;/*声道*/desired.samples=out_nb_samples;/*样本数1024*/desired.silence=0;/*静音值*/desired.callback=AudioCallback;SDL_OpenAudio(&desired,NULL);SDL_PauseAudio(0);/*开始播放音频,1为播放静音值*///分配内存audioframe=av_frame_alloc();/*分配音频帧*/printf("音频数据初始化完成");}//视频解码AVFrame *frame=av_frame_alloc();/*分配视频帧*/AVFrame *frameYUV=av_frame_alloc();/*申请yuv空间*//*分配空间,进行图像转换*/int width=ps->streams[videostream]->codecpar->width;int height=ps->streams[videostream]->codecpar->height;int fmt=ps->streams[videostream]->codecpar->format;/*流格式*/printf("fmt=%d\n",fmt);int size=avpicture_get_size(AV_PIX_FMT_RGB24, width,height);unsigned char *buff=NULL;printf("w=%d,h=%d,size=%d\n",width,height,size);buff=av_malloc(size);/*计算一帧空间大小*/avpicture_fill((AVPicture *)frameYUV,buff,AV_PIX_FMT_RGB24,width,height);/*转换上下文*/struct SwsContext *swsctx=sws_getContext(width,height, fmt,width,height, AV_PIX_FMT_RGB24,SWS_BICUBIC,NULL,NULL,NULL);/*读帧*/int go=0;int go_audio;  list_head=List_CreateHead(list_head);//创建链表头/*创建音频处理线程*/pthread_t pthid;pthread_create(&pthid,NULL,Audio_decode,(void *)ps);pthread_detach(pthid);//设置为分离属性/*创建窗口*/SDL_Window *window=SDL_CreateWindow("SDL_VIDEO", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,1280,720,SDL_WINDOW_SHOWN);/*创建渲染器*/SDL_Renderer *render=SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED);/*清空渲染器*/SDL_RenderClear(render);   /*创建纹理*/SDL_Texture *sdltext=SDL_CreateTexture(render,SDL_PIXELFORMAT_RGB24,SDL_TEXTUREACCESS_STREAMING,width,height);     bool quit=true;SDL_Event event;printf("read fream buff\n");//初始化转码器AVPacket *packet=av_malloc(sizeof(AVPacket));/*分配包*/av_init_packet(packet);//初始化int i=0;int index=0;long video_pts_time=0;long audio_pts_time=0;//音频解码时间time=(1000000/frame_rate-10000);//时间printf("time=%d\n",time);while((av_read_frame(ps,packet)>=0) && (quit)){SDL_PollEvent(&event);if(event.type==SDL_QUIT){quit=false;continue;}if(packet->stream_index == videostream)/*判断是否为视频*/{res=avcodec_send_packet(ps->streams[videostream]->codec,packet);if(res){av_packet_unref(packet);//释放这个pktcontinue;}res=avcodec_receive_frame(ps->streams[videostream]->codec,frame);if(res){av_packet_unref(packet);//释放这个pkt   continue;}sws_scale(swsctx,(const uint8_t **)frame->data,frame->linesize,0,height,(const uint8_t **)frameYUV->data,frameYUV->linesize);video_pts_time=packet->pts;//printf("视频=%ld\n",video_pts_time);SDL_UpdateTexture(sdltext,NULL,buff, width*3);SDL_RenderCopy(render, sdltext, NULL, NULL); // 拷贝纹理到渲染器SDL_RenderPresent(render); //渲染usleep(time);}if(packet->stream_index == audiostream)  //如果为音频标志{if(audiostream<0)continue;res=avcodec_send_packet(pCodecCtx,packet);if(res){printf("avcodec_send_packet failed,res=%d\n",res);av_packet_unref(packet);//释放这个pkt  continue;}res=avcodec_receive_frame(pCodecCtx,audioframe);if(res){printf("avcodec_receive_frame failed,res=%d\n",res);av_packet_unref(packet);//释放这个pktcontinue;}//数据格式转换res=swr_convert(swrCtx,&out_buffer,out_buffer_size,/*重采样之后的数据*/(const uint8_t **)audioframe->data,audioframe->nb_samples/*重采样之前数据*/);audio_pts_time=packet->pts;//printf("音频:%ld\n",audio_pts_time);if(res>0){//audio_chunk =out_buffer; //指向音频数据 (PCM data)//while(audio_len>0){}//等待数据处理完//audio_len =audioframe->nb_samples;//out_buffer_size;//音频长度//audio_pos =out_buffer;//当前播放位置List_AddNode(list_head,out_buffer,out_buffer_size);//添加节点}}//释放数据包av_packet_unref(packet);}sws_freeContext(swsctx);av_frame_free(&frame);av_frame_free(&frameYUV);avformat_free_context(ps);return 0;
}
/*创建链表头*/
struct AUDIO_DATA *List_CreateHead(struct AUDIO_DATA *head)
{if(head==NULL){head=malloc(sizeof(struct AUDIO_DATA));head->next=NULL;}return head;
}/*添加节点*/
void List_AddNode(struct AUDIO_DATA *head,unsigned char* audio_buffer,int audio_size)
{struct AUDIO_DATA *tmp=head;struct AUDIO_DATA *new_node;pthread_mutex_lock(&mutex);/*找到链表尾部*/while(tmp->next){tmp=tmp->next;}/*插入新的节点*/new_node=malloc(sizeof(struct AUDIO_DATA));new_node->audio_size=audio_size;new_node->audio_buffer=malloc(audio_size);//分配保存音频数据大小空间memcpy(new_node->audio_buffer,audio_buffer,audio_size);new_node->next=NULL;/*将新节点接入到链表*/tmp->next=new_node;  pthread_mutex_unlock(&mutex);
}
/*
函数功能:删除节点
*/
void List_DelNode(struct AUDIO_DATA *head,unsigned char* audio_buffer)
{struct AUDIO_DATA *tmp=head;struct AUDIO_DATA *p;/*找到链表中要删除的节点*/pthread_mutex_lock(&mutex);while(tmp->next){p=tmp;tmp=tmp->next;if(tmp->audio_buffer==audio_buffer){p->next=tmp->next;free(tmp->audio_buffer);free(tmp);break;}}pthread_mutex_unlock(&mutex);
}
/*
函数功能:遍历链表,得到节点总数量
*/
int List_GetNodeCnt(struct AUDIO_DATA *head)
{int cnt=0;struct AUDIO_DATA *tmp=head;pthread_mutex_lock(&mutex);while(tmp->next){tmp=tmp->next;cnt++;}pthread_mutex_unlock(&mutex);return cnt;
}
/*
从链表头取数据
*/
int List_GetNode(struct AUDIO_DATA *head,char *audio_buff,int *audio_size)
{struct AUDIO_DATA *tmp=head;struct AUDIO_DATA *ptemp=head;pthread_mutex_lock(&mutex);while(tmp->next!=NULL){ptemp=tmp;tmp=tmp->next;if(tmp!=NULL){*audio_size=tmp->audio_size;memcpy(audio_buff,tmp->audio_buffer,tmp->audio_size);ptemp->next=tmp->next;free(tmp->audio_buffer);free(tmp);pthread_mutex_unlock(&mutex);return 0;}}pthread_mutex_unlock(&mutex);return -1;
}
  • Makefile文件
OBJ=main.o
CFLAGS=-I/home/wbyq/src_pack/ffmpeg-4.2.5/_install/include -L/home/wbyq/src_pack/ffmpeg-4.2.5/_install/lib\
-I/home/wbyq/src_pack/SDL2-2.0.14/_install/include -I/home/wbyq/src_pack/SDL2-2.0.14/_install/include/SDL2 -L/home/wbyq/src_pack/SDL2-2.0.14/_install/lib \
-lSDL2 -lpthread -lm -ldl  -lavcodec -lavfilter -lavutil -lswresample -lavdevice -lavformat -lpostproc -lswscale -lpthread -lstdc++ -lm -lasound -lx264
app:$(OBJ)gcc -o $@ $^ $(CFLAGS)  
  • 运行示例


    FFMPEG安装教程:https://blog.csdn.net/weixin_44453694/article/details/121978238

Linux下基于ffmpeg音视频解码相关推荐

  1. 嵌入式Linux下基于FFmpeg的视频硬件编解码[图]

    转自:http://tech.c114.net/167/a674033.html 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究.结合对S3C6410处理器视频硬件编解码方法的分析,阐 ...

  2. 嵌入式Linux下基于FFmpeg的视频硬件编解码

    嵌入式Linux下基于FFmpeg的视频硬件编解码[图] http://www.c114.net ( 2012/3/1 15:41 ) 摘要: 对FFmpeg多媒体解决方案中的视频编解码流程进行研究. ...

  3. [学习笔记]基于ffmpeg的视频解码,输出YUV图像到文件。

    虽然在音视频领域工作了一段时间,但是对于ffmpeg还是比较陌生,从这周开始入手学习. 拜读了雷霄骅的<最简单的基于FFMPEG+SDL的视频播放器 ver2>,受益匪浅,链接如下 htt ...

  4. linux下的ffmpeg 用法

    简述:ffmpeg是一个非常好的转码工具. 将视频文件1.avi和音频文件1.wav合成音视频文件1.mp4 ffmpeg -i 1.avi -i 1.wav -threads 1 -strict - ...

  5. 嵌入式linux下的FFmpeg交叉编译(最全面)

    FFmpeg介绍 当下直播应用很火,在音视频领域,FFmpeg相当知名.可能你用的一些视频播放器背后都有它的身影.FFmpeg是一个开源的跨平台多媒体处理工具,可以用于处理音视频流.转码.封装.解封装 ...

  6. FFMPEG音视频解码

    1.播放多媒体文件步骤 通常情况下,我们下载的视频文件如MP4,MKV.FLV等都属于封装格式,就是把音视频数据按照相应的规范,打包成一个文本文件.我们可以使用MediaInfo这个工具查看媒体文件的 ...

  7. Linux下基于密钥的安全验证实现方法

    Linux下基于密钥的安全验证实现方法 -------OpenSSH+WinSCP+putty密钥生成器+putty 实验背景: 小诺公司目前已使用Linux搭建了各个服务器(FTP.DNS.Apac ...

  8. linux 下基于jrtplib库的实时传送实现

    linux 下基于jrtplib库的实时传送实现 一.RTP 是进行实时流媒体传输的标准协议和关键技术 实时传输协议(Real-time Transport Protocol,PRT)是在 Inter ...

  9. Linux下基于Libmad库的MP3音乐播放器编写

    linux下基于Libmad库的MP3音乐播放器编写 libmad是一个开源mp3解码库,其对mp3解码算法做了很多优化,性能较好,很多播放器如mplayer.xmms等都是使用这个开源库进行解码的: ...

最新文章

  1. 使用apply调用函数
  2. linux下java调用python脚本,java - 在Linux Terminal中以编程方式从Java调用python脚本 - 堆栈内存溢出...
  3. python下载文件传到服务器_python实现FTP文件传输的方法(服务器端和客户端)
  4. 【ASP.NET Core】给路由规则命名有何用处
  5. table 内 下拉列表 被遮挡_一个简洁、有趣的无限下拉方案
  6. 一次性说清楚秒验(本机号码一键登录)基本原理、优势、场景、交互过程和常见的问题
  7. Windows 下安装 laravel框架
  8. World Wind Java开发之二 使用Winbuilders设计图形用户界面(转)
  9. java三次登录锁定_Java基础知识点有哪些 如何快速步入Java行业
  10. C++向函数传递数组
  11. 如何用纯 CSS 创作一个 3D 文字跑马灯特效
  12. 拼音获取啊啊的的js
  13. LiquidCrystal-I2C
  14. dos 教程(很全的)
  15. JESD204B协议基础知识
  16. #P00603. 倒水
  17. 【SecureFx服务器无法上传文中文件】
  18. RocketMQ实战疑问和原理解答(更新至Q9)
  19. C++优化之使用emplace、emplace_back
  20. 门铃质检报告办理快速发证

热门文章

  1. PostgreSQL备份工具pg_dump和pg_dumpall
  2. Spring构造器的三种注入方式
  3. windows之nslookup命令
  4. 武田通过与Moderna和日本政府合作,在日本扩大COVID-19疫苗供货
  5. 一、网上商城推荐系统
  6. VC++一文带你搞懂如何操作文件对话框(附源码)
  7. java统计string中文数字英文_Java学习(4):统计一个文件中的英文,中文,数字,其他字符以及字符总数...
  8. [转]AppCompat 22.1,Goole暴走,MD全面兼容低版本
  9. iOS 三方登录 微信登录失败 真机测试 由于应用BundleID信息校验不通过,无法使用微信登录
  10. docker容器使用