ffmpeg拉rtmp流并用SDL播放
开发环境
操作系统:win10
IDE:vs2019
ffmpeg版本:5.1
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include <SDL/SDL.h>
#define MAX_AUDIO_FRAME_SIZE 192000 // 1 second of 48khz 32bit audio
void notifyGetAudioFrame() {
SDL_Event sdlEvent;
//sdlEvent.type = SDL_EVENT_BUFFER_END;
SDL_PushEvent(&sdlEvent);
}
//Buffer:
//|-----------|-------------|
//chunk-------pos---len-----|
static Uint8* audio_chunk = 0;
static Uint32 audio_len = 0;
static Uint8* audio_pos = 0;
/* The audio function callback takes the following parameters:
* stream: A pointer to the audio buffer to be filled
* len: The length (in bytes) of the audio buffer
*/
static void 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); /* Mix as much data as possible */
//memcpy(stream, audio_pos, len);
SDL_MixAudioFormat(stream, audio_pos, AUDIO_S16SYS, len, SDL_MIX_MAXVOLUME);
//SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);
audio_pos += len;
audio_len -= len;
//if (audio_len <= 0) {
// // 读取完了,通知继续读取数据
// notifyGetAudioFrame();
//}
}
//-----------------
int main(int argc, char* argv[])
{
const AVOutputFormat* ofmt = NULL;
//Input AVFormatContext
AVFormatContext* ifmt_ctx = NULL;
AVPacket* pkt;
const char* in_filename, * out_filename;
int ret, i;
int videoindex = -1;
int audioindex = -1;
int frame_index = 0;
in_filename = "rtmp://ns8.indexforce.com/home/mystream";
int screen_w = 0, screen_h = 0;
SDL_Window* screen;
int w, h;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
//视频解码器
AVCodecContext* pVideoCodecCtx = NULL;
AVCodecParameters* pVideoCodecPara = NULL;
AVCodec* pVideoCodec = NULL;
//音频解码器
AVCodecContext* pAudioCodecCtx = NULL;
AVCodecParameters* pAudioCodecPara = NULL;
AVCodec* pAudioCodec = NULL;
AVFrame* pFrame = NULL;
AVFrame* pFrameYUV = NULL;
struct SwsContext* img_convert_ctx = NULL;
//Out Audio Param
int out_nb_samples;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
int out_sample_rate = 44100;
//SDL_AudioSpec
SDL_AudioSpec wanted_spec;
uint8_t* out_buffer = NULL;
AVChannelLayout outChannelLayout = AV_CHANNEL_LAYOUT_STEREO;
AVChannelLayout inChannelLayout;
SDL_AudioDeviceID deviceId;
FILE* pFile = NULL;
fopen_s(&pFile, "output.pcm", "wb");
//Network
avformat_network_init();
//Input
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
printf("Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
printf("Failed to retrieve input stream information");
goto end;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++)
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoindex = i;
break;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++)
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioindex = i;
break;
}
pVideoCodecPara = ifmt_ctx->streams[videoindex]->codecpar;
pVideoCodec = (AVCodec*)avcodec_find_decoder(pVideoCodecPara->codec_id);
if (pVideoCodec == NULL)
{
printf("Codec not found.\n");
goto end;
}
pVideoCodecCtx = avcodec_alloc_context3(pVideoCodec);
avcodec_parameters_to_context(pVideoCodecCtx, pVideoCodecPara);
if (pVideoCodecCtx == NULL) {
printf("Cannot alloc context.");
goto end;
}
if (avcodec_open2(pVideoCodecCtx, pVideoCodec, NULL) < 0)
{
printf("Could not open codec.\n");
goto end;
}
pAudioCodecPara = ifmt_ctx->streams[audioindex]->codecpar;
pAudioCodec = (AVCodec*)avcodec_find_decoder(pAudioCodecPara->codec_id);
if (pAudioCodec == NULL)
{
printf("Codec not found.\n");
goto end;
}
pAudioCodecCtx = avcodec_alloc_context3(pAudioCodec);
avcodec_parameters_to_context(pAudioCodecCtx, pAudioCodecPara);
if (pAudioCodecCtx == NULL) {
printf("Cannot alloc context.");
goto end;
}
if (avcodec_open2(pAudioCodecCtx, pAudioCodec, NULL) < 0)
{
printf("Could not open codec.\n");
goto end;
}
av_dump_format(ifmt_ctx, 0, in_filename, 0);
screen_w = 640;
screen_h = 480;
screen = SDL_CreateWindow("B_Play", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
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, screen_w, screen_h);
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
pkt = av_packet_alloc();
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();
pFrameYUV->width = pVideoCodecCtx->width;
pFrameYUV->height = pVideoCodecCtx->height;
pFrameYUV->format = AV_PIX_FMT_YUV420P;
av_frame_get_buffer(pFrameYUV, 1);
img_convert_ctx = sws_getContext(pVideoCodecCtx->width, pVideoCodecCtx->height, pVideoCodecCtx->pix_fmt,
screen_w, screen_h, AV_PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL);
//SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO);
out_nb_samples = pAudioCodecCtx->frame_size;
//out_sample_rate = pAudioCodecCtx->sample_rate;
wanted_spec.freq = out_sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = 2;
wanted_spec.silence = 0;
wanted_spec.samples = out_nb_samples;
wanted_spec.callback = fill_audio;
wanted_spec.userdata = NULL;
if ((deviceId = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, NULL, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE)) < 2) {
return-1;
}
out_buffer = (uint8_t*)av_malloc(out_nb_samples * 4);
//outChannelLayout = AV_CHANNEL_LAYOUT_STEREO;
inChannelLayout = pAudioCodecCtx->ch_layout;
outChannelLayout.nb_channels = 2;
inChannelLayout.nb_channels = pAudioCodecCtx->ch_layout.nb_channels;
struct SwrContext* au_convert_ctx;
au_convert_ctx = swr_alloc();
swr_alloc_set_opts2(&au_convert_ctx, &outChannelLayout, out_sample_fmt, out_sample_rate,
&inChannelLayout, pAudioCodecCtx->sample_fmt, pAudioCodecCtx->sample_rate, 0, NULL);
swr_init(au_convert_ctx);
SDL_PauseAudioDevice(deviceId, 0);
while (1) {
//Get an AVPacket
ret = av_read_frame(ifmt_ctx, pkt);
if (ret < 0)
break;
if (pkt->stream_index == videoindex) {
if (avcodec_send_packet(pVideoCodecCtx, pkt) == 0) {
while (avcodec_receive_frame(pVideoCodecCtx, pFrame) == 0) {
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pVideoCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize);
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
pFrameYUV->data[0], pFrameYUV->linesize[0],
pFrameYUV->data[1], pFrameYUV->linesize[1],
pFrameYUV->data[2], pFrameYUV->linesize[2]);
SDL_RenderClear(sdlRenderer);
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent(sdlRenderer);
SDL_Delay(40);
}
}
i++;//只计算视频帧
}
else if (pkt->stream_index == audioindex) {
if (avcodec_send_packet(pAudioCodecCtx, pkt) == 0) {
while (avcodec_receive_frame(pAudioCodecCtx, pFrame) == 0) {
int nRet = swr_convert(au_convert_ctx, &out_buffer, out_nb_samples, (const uint8_t**)pFrame->data, pFrame->nb_samples);
int buffSize = av_samples_get_buffer_size(NULL, 2, nRet, out_sample_fmt, 1);
fwrite(out_buffer, 1, buffSize, pFile);
while (audio_len > 0)//Wait until finish
SDL_Delay(1);
//Set audio buffer (PCM data)
audio_chunk = (Uint8*)out_buffer;
//Audio buffer length
audio_len = buffSize;
audio_pos = audio_chunk;
//av_packet_unref(pkt);//重置pkt的内容
}
}
}
av_packet_unref(pkt);
}
av_packet_free(&pkt);
end:
if (pFrame) {
av_frame_free(&pFrame);
}
if (pFrameYUV) {
av_frame_free(&pFrameYUV);
}
if (pVideoCodecCtx) {
avcodec_close(pVideoCodecCtx);
}
if (pAudioCodecCtx) {
avcodec_close(pAudioCodecCtx);
}
SDL_CloseAudio();//Close SDL
SDL_Quit();
avformat_close_input(&ifmt_ctx);
if (ret < 0 && ret != AVERROR_EOF) {
printf("Error occurred.\n");
return -1;
}
return 0;
}
视频播放没有问题
音频单独保存成pcm文件播放没问题,但是用SDL直接播放会有电流等杂音,原因还没有找到。
完善
代码进行了重构,音频播放也没有问题了。