缅怀雷大神:https://blog.csdn.net/leixiaohua1020/article/details/38868499
- 经过前面两章,我们发现音频播放器和视频播放器有很多相似的代码
- 我们将用类将其简易的封装起来
一、基类Player
Player.h
#ifndef PLAYER_H
#define PLAYER_H
extern "C"
{
//封装格式
#include "libavformat/avformat.h"
//解码
#include "libavcodec/avcodec.h"
//缩放
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
//播放
#include "SDL2/SDL.h"
};
class Player
{
public:
Player();
~Player();
int openFile(char *filepath, int type);
virtual int play() = 0;
protected:
AVFormatContext *pFormatCtx;
AVCodecContext *pCodeCtx;
AVCodec *pCodec;
int index;
};
#endif
Player.cpp
#include "Player.h"
Player::Player()
{
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf("Could not initialize SDL - %s\n", SDL_GetError());
}
}
Player::~Player()
{
SDL_Quit();
avcodec_close(pCodeCtx);
avformat_close_input(&pFormatCtx);
}
int Player::openFile(char *filepath, int type)
{
//打开输入文件
if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0){
printf("Couldn't open input stream.\n");
return -1;
}
//获取文件信息
if (avformat_find_stream_info(pFormatCtx, NULL)<0){
printf("Couldn't find stream information.\n");
return -1;
}
//找到对应的type所在的pFormatCtx->streams的索引位置
index = -1;
for (int i = 0; i<pFormatCtx->nb_streams; i++)
if (pFormatCtx->streams[i]->codec->codec_type == type){
index = i;
break;
}
if (index == -1){
printf("Didn't find a video stream.\n");
return -1;
}
//获取解码器
pCodeCtx = pFormatCtx->streams[index]->codec;
pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
if (pCodec == NULL){
printf("Codec not found.\n");
return -1;
}
//打开解码器
if (avcodec_open2(pCodeCtx, pCodec, NULL)<0){
printf("Could not open codec.\n");
return -1;
}
}
二、 音频播放器
MusicPlayer.h
#ifndef MUSICPLAYER_H
#define MUSICPLAYER_H
#include "Player.h"
class MusicPlayer:public Player
{
public:
MusicPlayer();
~MusicPlayer();
private:
SwrContext *swrCtx;
//重采样设置选项-----------------------------------------------------------start
//输入的采样格式
enum AVSampleFormat in_sample_fmt;
//输出的采样格式 16bit PCM
enum AVSampleFormat out_sample_fmt;
//输入的采样率
int in_sample_rate;
//输出的采样率
int out_sample_rate;
//输入的声道布局
uint64_t in_ch_layout;
//输出的声道布局
uint64_t out_ch_layout;
//重采样设置选项-----------------------------------------------------------end
//SDL_AudioSpec
SDL_AudioSpec wanted_spec;
//获取输出的声道个数
int out_channel_nb;
public:
void audioSetting();
int setAudioSDL();
int play();
private:
static void fill_audio(void *udata, Uint8 *stream, int len);
};
#endif
MusicPlayer.cpp
#include "MusicPlayer.h"
MusicPlayer::MusicPlayer()
{
}
MusicPlayer::~MusicPlayer()
{
SDL_CloseAudio();//关闭音频设备
swr_free(&swrCtx);
}
void MusicPlayer::audioSetting()
{
//重采样设置选项-----------------------------------------------------------start
//输入的采样格式
in_sample_fmt = pCodeCtx->sample_fmt;
//输出的采样格式 16bit PCM
out_sample_fmt = AV_SAMPLE_FMT_S16;
//输入的采样率
in_sample_rate = pCodeCtx->sample_rate;
//输出的采样率
out_sample_rate = 44100;
//输入的声道布局
in_ch_layout = pCodeCtx->channel_layout;
if (in_ch_layout <= 0)
{
in_ch_layout = av_get_default_channel_layout(pCodeCtx->channels);
}
//输出的声道布局
out_ch_layout = AV_CH_LAYOUT_MONO;
//frame->16bit 44100 PCM 统一音频采样格式与采样率
swrCtx = swr_alloc();
swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt,
in_sample_rate, 0, NULL);
swr_init(swrCtx);
//重采样设置选项-----------------------------------------------------------end
}
int MusicPlayer::setAudioSDL()
{
//获取输出的声道个数
out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
//SDL_AudioSpec
wanted_spec.freq = out_sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = out_channel_nb;
wanted_spec.silence = 0;
wanted_spec.samples = pCodeCtx->frame_size;
wanted_spec.callback = fill_audio;//回调函数
wanted_spec.userdata = pCodeCtx;
if (SDL_OpenAudio(&wanted_spec, NULL)<0){
printf("can't open audio.\n");
return -1;
}
}
static Uint8 *audio_chunk;
//设置音频数据长度
static Uint32 audio_len;
static Uint8 *audio_pos;
void MusicPlayer::fill_audio(void *udata, Uint8 *stream, int len){
//SDL 2.0
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;
}
int MusicPlayer::play()
{
//编码数据
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//解压缩数据
AVFrame *frame = av_frame_alloc();
//存储pcm数据
uint8_t *out_buffer = (uint8_t *)av_malloc(2 * 44100);
int ret, got_frame, framecount = 0;
//一帧一帧读取压缩的音频数据AVPacket
while (1)
while (av_read_frame(pFormatCtx, packet) >= 0) {
if (packet->stream_index == index) {
//解码AVPacket->AVFrame
ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);
if (ret < 0) {
printf("%s", "解码完成");
}
//非0,正在解码
int out_buffer_size;
if (got_frame) {
//printf("解码%d帧", framecount++);
swr_convert(swrCtx, &out_buffer, 2 * 44100, (const uint8_t **)frame->data, frame->nb_samples);
//获取sample的size
out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples,
out_sample_fmt, 1);
//设置音频数据缓冲,PCM数据
audio_chunk = (Uint8 *)out_buffer;
//设置音频数据长度
audio_len = out_buffer_size;
audio_pos = audio_chunk;
//回放音频数据
SDL_PauseAudio(0);
while (audio_len>0)//等待直到音频数据播放完毕!
SDL_Delay(10);
packet->data += ret;
packet->size -= ret;
}
}
}
av_free(out_buffer);
av_frame_free(&frame);
av_free_packet(packet);
return 0;
}
三、视频播放器
VideoPlayer.h
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
#include "Player.h"
class VideoPlayer:public Player
{
public:
VideoPlayer();
~VideoPlayer();
private:
struct SwsContext *img_convert_ctx;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Thread *video_tid;
SDL_Event event;
public:
void showInfo();
int setWindow();
virtual int play();
static int sfp_refresh_thread(void *opaque);
};
#endif
VideoPlayer.cpp
#include "VideoPlayer.h"
VideoPlayer::VideoPlayer()
{
}
VideoPlayer::~VideoPlayer()
{
}
void VideoPlayer::showInfo()
{
printf("---------------- File Information ---------------\n");
av_dump_format(pFormatCtx, 0, "屌丝男士.mov", 0);
printf("-------------------------------------------------\n");
}
int VideoPlayer::setWindow()
{
//SDL 2.0 Support for multiple windows
/*screen_w = pCodeCtx->width;
screen_h = pCodeCtx->height;*/
int screen_w = 800;
int screen_h = 600;
SDL_Window *screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h, SDL_WINDOW_OPENGL);
if (!screen) {
printf("SDL: could not create window - exiting:%s\n", SDL_GetError());
return -1;
}
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, pCodeCtx->width, pCodeCtx->height);
}
int VideoPlayer::sfp_refresh_thread(void *opaque){
while (1)
{
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
return 0;
}
int VideoPlayer::play()
{
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//内存分配
AVFrame *pFrame = av_frame_alloc();
AVFrame *pFrameYUV = av_frame_alloc();
//缓冲区分配内存
unsigned char *out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height, 1));
//初始化缓冲区
av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,
AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height, 1);
img_convert_ctx = sws_getContext(pCodeCtx->width, pCodeCtx->height, pCodeCtx->pix_fmt,
pCodeCtx->width, pCodeCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
int ret, got_picture;
SDL_CreateThread(sfp_refresh_thread, NULL, NULL);
while (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == index)
{
ret = avcodec_decode_video2(pCodeCtx, pFrame, &got_picture, packet);
if (ret < 0){
printf("Decode Error.\n");
}
if (got_picture)
{
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodeCtx->height, pFrameYUV->data, pFrameYUV->linesize);
}
while (1)
{
SDL_WaitEvent(&event);
if (event.type == SFM_REFRESH_EVENT)
{
//SDL---------------------------
SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent(sdlRenderer);
//SDL End-----------------------
break;
}
}
}
}
sws_freeContext(img_convert_ctx);
av_free(out_buffer);
av_free_packet(packet);
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
return 0;
}
四、视频和音频同时播放
main.cpp
#include "VideoPlayer.h"
#include "MusicPlayer.h"
int audioThread(void *opaque){
MusicPlayer music;
music.openFile("G:/迅雷下载/老师好.mp4", AVMEDIA_TYPE_AUDIO);
music.audioSetting();
music.setAudioSDL();
music.play();
return 0;
}
int main(int argc, char* argv[])
{
VideoPlayer play;
play.openFile("G:/迅雷下载/老师好.mp4", AVMEDIA_TYPE_VIDEO);
play.showInfo();
play.setWindow();
SDL_Thread *video_tid = SDL_CreateThread(audioThread, NULL, NULL);
play.play();
SDL_WaitThread(video_tid, NULL);
return 0;
}
注意:当电脑轻微卡顿时,会出现声音和画面不同步
后续进行优化...