在Qt中,可以使用FFmpeg库和Live555库来实现基于RTP协议的H.264编码视频流和音频流的发送和接收。下面分别介绍如何实现。
发送端
准备工作
首先需要下载安装Live555库,并将其添加到Qt项目中。具体步骤如下:
- 下载最新版本的Live555源代码;
- 解压缩后,进入目录
live
,执行命令./genMakefiles linux-clang
生成Makefile文件; - 执行命令
make
进行编译; - 在Qt项目中添加头文件路径、库文件路径和链接库即可。
除此之外,还需要在Qt项目中引入FFmpeg库,方法同上。
实现过程
接下来是一个简单的示例代码,用于将本地摄像头捕获的视频流进行H.264编码,并通过RTP协议进行发送:
#include <QCoreApplication>
#include <QDebug>
#include <QTimer>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"
#include "GroupsockHelper.hh"
#define VIDEO_PAYLOAD_TYPE 96
#define AUDIO_PAYLOAD_TYPE 97
class Sender {
public:
Sender() : env_(nullptr), task_(nullptr), mediaSession_(nullptr),
videoEncoderCtx_(nullptr), audioEncoderCtx_(nullptr) {}
~Sender() { deleteLater(); }
void init(const QString& destIpAddress, quint16 destPort) {
env_ = BasicUsageEnvironment::createNew();
task_ = EventLoopTask::createNew(*env_);
// 创建视频编码器上下文
videoEncoderCtx_ = avcodec_alloc_context3(nullptr);
videoEncoderCtx_->codec_id = AV_CODEC_ID_H264;
videoEncoderCtx_->bit_rate = 5000000;
videoEncoderCtx_->width = 640;
videoEncoderCtx_->height = 480;
videoEncoderCtx_->time_base.num = 1;
videoEncoderCtx_->time_base.den = 30;
AVCodec *videoCodec = avcodec_find_encoder(videoEncoderCtx_->codec_id);
if (!videoCodec) {
qDebug() << "Failed to find H.264 encoder.";
return;
}
if (avcodec_open2(videoEncoderCtx_, videoCodec, nullptr) < 0) {
qDebug() << "Failed to open H.264 encoder.";
return;
}
// 创建音频编码器上下文
audioEncoderCtx_ = avcodec_alloc_context3(nullptr);
audioEncoderCtx_->sample_fmt = AV_SAMPLE_FMT_S16P;
audioEncoderCtx_->sample_rate = 44100;
audioEncoderCtx_->channels = 2;
AVCodec *audioCodec = avcodec_find_encoder_by_name("pcm_s16le");
if (!audioCodec) {
qDebug() << "Failed to find PCM audio encoder.";
return;
}
if (avcodec_open2(audioEncoderCtx_, audioCodec, nullptr) < 0) {
qDebug() << "Failed to open PCM audio encoder.";
return;
}
// 创建媒体会话和媒体流
mediaSession_ =
MediaSession::createNew(*env_, "live555Session", "Live streaming session");
MediaStream *videoStream = MediaStream::createNew(*env_, videoEncoderCtx_);
MediaStream *audioStream = MediaStream::createNew(*env_, audioEncoderCtx_);
mediaSession_->addSubsession(videoStream->newClone());
mediaSession_->addSubsession(audioStream->newClone());
// 创建RTP发送器
RTPSink *videoSink =
H264VideoRTPSink::createNew(*env_, &rtpGroupsock_,
VIDEO_PAYLOAD_TYPE, 90000, destIpAddress.toUtf8().constData(),
destPort);
RTPSink *audioSink =
SimpleRTPSink::createNew(*env_, &rtpGroupsock_,
AUDIO_PAYLOAD_TYPE, 44100,
destIpAddress.toUtf8().constData(), destPort+2);
videoStream->addDestination(videoSink);
audioStream->addDestination(audioSink);
}
void start() {
task_->scheduleDelayedTask(0, (TaskFunc*)Sender::startSending, this);
env_->taskScheduler().doEventLoop(&eventLoopWatchVariable_);
}
private:
static void startSending(Sender* sender) {
AVFrame *frame = av_frame_alloc();
uint8_t *frameBuffer = new uint8_t[640 * 480 * 3 / 2];
uint8_t **framePlane = { frameBuffer, frameBuffer + 640*480,
frameBuffer + 640*480*5/4 };
QTimer timer;
timer.setInterval(33); // 每秒30帧
QObject::connect(&timer, &QTimer::timeout, [sender, frame]() {
// 捕获一帧视频帧和一段音频数据
QImage image = QApplication::primaryScreen()->grabWindow(0).toImage();
avpicture_fill((AVPicture*)frame, image.bits(), AV_PIX_FMT_RGB32,
image.width(), image.height());
QByteArray audioData;
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QIODevice *device = QAudioDeviceInfo::defaultOutputDevice().start();
if (device && device->isOpen()) {
QAudioOutput output(format);
output.start(device);
// 生成一段5秒钟的音频数据
qint64 numSamples = 44100 * 5;
qint16 sampleValue = 32767 / 3;
for (int i = 0; i < numSamples; ++i)
audioData.append(reinterpret_cast<const char*>(&sampleValue), sizeof(sampleValue));
output.write(audioData.data(), audioData.size());
while (output.state() == QAudio::ActiveState) {
if (output.bytesFree() < audioData.size())
continue;
output.write(audioData.data(), audioData.size());
break;
}
delete device;
}
// 视频编码
int ret = avcodec_send_frame(sender->videoEncoderCtx_, frame);
while (ret >= 0) {
AVPacket pkt;
av_init_packet(&pkt);
ret = avcodec_receive_packet(sender->videoEncoderCtx_, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
pkt.pts = pkt.dts = av_rescale_q(pkt.pts, sender->videoEncoderCtx_->time_base,
{1,90000});
pkt.duration = av_rescale_q(pkt.duration, sender->videoEncoderCtx_->time_base,
{1,90000});
// 发送RTP数据包
RTPSink::sendPacket(sender->mediaSession_, &pkt);
av_packet_unref(&pkt);
}
delete frame;
});
timer.start();
}
void deleteLater() {
delete videoEncoderCtx_;
delete audioEncoderCtx_;
Medium::close(mediaSession_);
BasicTaskScheduler::deleteBasicTaskScheduler(env_);
}
private:
TaskScheduler *env_;
EventLoopTask *task_;
MediaSession *mediaSession_;
Groupsock rtpGroupsock_;
AVCodecContext *videoEncoderCtx_;
AVCodecContext *audioEncoderCtx_;
private:
int eventLoopWatchVariable_{0};
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Sender sender;
sender.init("192.168.1.100", 5000);
sender.start();
return a.exec();
}
这个例子中,首先创建了一个Sender
类,用于将本地摄像头捕获的视频流进行H.264编码,并通过RTP协议进行发送。在初始化时,需要指定目标IP地址和端口号。
接着,创建视频编码器上下文和音频编码器上下文,并分别设置一些参数,如编码方式、码率、分辨率等。然后,创建媒体会话和媒体流,并添加到会话中。
接下来是关键步骤:使用Live555库提供的RTP发送器,将视频流和音频流发送出去。创建RTP发送器需要指定负载类型、时间戳等参数,并将其添加到对应的媒体流中。
最后,在一个定时器回调函数中,捕获一帧视频帧和一段音频数据,并将其进行编码。编码完成后,将数据封装成RTP包,并使用RTPSink::sendPacket()
方法进行发送。需要注意的是,在发送之前要先设置好时间戳等参数。
接收端
准备工作
接收端同样也需要引入Live555库和FFmpeg库。
实现过程
下面是一个简单的示例代码,用于从网络上接收并解码H.264编码视频流和音频流:
”`cpp
#include
extern “C” {
#include
#include “liveMedia.hh” #include “BasicUsageEnvironment.hh” #include “GroupsockHelper.hh”
class Receiver : public MediaSession::SubsessionIterator { public:
Receiver() : env_(nullptr), task_(nullptr), mediaSession_(nullptr),
videoDecoderCtx_(nullptr), audioDecoderCtx_(nullptr) {}
~Receiver() { deleteLater(); }
void init(const QString& srcIpAddress, quint16 srcPort) {
env_ = BasicUsageEnvironment::createNew();
task_ = EventLoopTask::createNew(*env_);
// 打开网络连接
const char *urlFmt = "rtp://%s:%d";
char url[1024];
sprintf(url, urlFmt, srcIpAddress.toUtf8().constData(), srcPort);
mediaSession_ = MediaSession::createNew(*env_, url);
if (!mediaSession_->hasSubsessions()) {
qDebug
- 准备工作
- 实现过程
- 准备工作
- 实现过程