前面已经实现了用类封装自己实现的格式转换功能,现在怎么把自己的功能打包好让别人方便用呢?本文学习了怎么用Visual Studio来进行封装打包。
一、动态链接库模板
Visual Studio提供了动态链接库模板,咱们新建项目,选择模板就行,非常方便
新建完得到四个这样的文件,用它给的文件怕出什么问题,咱们自己新建几个文件写自己的代码。
二、头文件定义
头文件是最关键的地方,让其他人直接通过我们的头文件来访问我们的代码,这里就是接口处了。头文件要尽量简单,把类的定义丢上来就差不多了,其他的我们在cpp文件里再实现。而且头文件里除了系统库不要再去include其他自己定义的库啥的了,不然别人用的时候找不到include的东西。
//MyTranscoder.h
#pragma once
#include <stdlib.h>
#include <stdint.h>
#include <string>
#ifdef MYTRANSCODER_EXPORTS
#define MYTRANSCODER_API __declspec(dllexport)
#else
#define MYTRANSCODER_API __declspec(dllimport)
#endif
class MyTranscoderImpl;
class MYTRANSCODER_API NoCopyable
{
protected:
NoCopyable() = default;
virtual ~NoCopyable() = default;
NoCopyable(NoCopyable const& other) = delete;
NoCopyable& operator=(NoCopyable const& other) = delete;
NoCopyable(NoCopyable&& other) = delete;
NoCopyable& operator=(NoCopyable&& other) = delete;
};
class MYTRANSCODER_API MyTranscoder : public NoCopyable {
public:
MyTranscoder();
~MyTranscoder() override;
bool transCode();
private:
MyTranscoderImpl* fImpl;
};
这里还有一个很关键的地方,我们用的是动态库dll编译,要想得到静态库lib就得加上导出标志MYTRANSCODER_API。另外,在工程设置里面,属性-->C/C++-->预处理器-->预处理器定义,把MYTRANSCODER_EXPORTS宏定义加进去就可以了。
为了让头文件更加简洁,我们这里的MyTranscoder类只是单纯的做一个接口,我们再额外实现一个MyTranscoderImpl类来进行功能的实现.
MyTranscoderImpl.h文件就随便include啥文件进来都没关系了,反正别人使用的时候不需要管除了接口头文件其他的东西,我们直接把要用到的ffmpeg的库全在这里引入进来。
//MyTranscoderImpl.h
#pragma once
#include <string>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#include <thread>
#include <mutex>
#include <condition_variable>
#include "SCOPEG.h"
#include <queue>
//此处添加与当前类声名相关的额外头文件
class MyTranscoderImpl {
public:
MyTranscoderImpl();
~MyTranscoderImpl();
bool formatConver();
private:
void decodeThread1();
void encodeThread1();
//解码参数包结构体
struct DecodeParamPacket {
std::string inputFile;
AVFormatContext* inputFormatContext;
AVStream* audioStream;
int videoStreamIndex;
int audioStreamIndex;
AVStream* videoStream;
};
//编码参数包结构体
struct EncodeParamPacket {
std::string outputFileName;
std::string format;
AVStream* audioStream;
AVStream* videoStream;
};
DecodeParamPacket fDecodeParam;
EncodeParamPacket fEncodeParam;
queue<AVFrame*> fFrameQueue;//帧队列
std::mutex fMtx; // 互斥量,保证线程访问的互斥
std::condition_variable fCv; // 条件变量,用于线程之间的通信
int fRet = 0;
bool fEnd = false;//全局结束标志
};
三、对应的功能实现cpp文件
首先MyTranscoder.h对应的MyTranscoder.cpp这里只是写个接口,所以cpp文件也就随便写点就行
//MyTranscoder.cpp
#include "pch.h"
#include "MyTranscoderImpl.h"
#include "../include/MyTranscoder.h"
MyTranscoder::MyTranscoder() {
fImpl = new MyTranscoderImpl();
}
MyTranscoder::~MyTranscoder() {
delete fImpl;
}
bool MyTranscoder::transCode() {
return fImpl->formatConver();
}
然后是MyTranscoderImpl.cpp,把我们之前写的解码编码所有功能都往这里放。
#include "pch.h"
#include "MyTranscoderImpl.h"
//此处添加与当前类实现相关的额外头文件
MyTranscoderImpl::MyTranscoderImpl() {
}
MyTranscoderImpl::~MyTranscoderImpl() {
}
bool MyTranscoderImpl::formatConver()
{
std::string inputFileName, outputFileName, format;
std::cout << "请输入输入文件名(带后缀):";
std::cin >> inputFileName;
std::cout << "请输入输出格式(avi,mp4,wmv,mkv,flv...):";
std::cin >> format;
std::cout << "请输入输出文件名(带后缀):";
std::cin >> outputFileName;
/*inputFilename = "cartoonTrim.mp4";
Format = "avi";
outputFilename = "Multithreading.avi";*/
fDecodeParam.inputFile = inputFileName;
fEncodeParam.outputFileName = outputFileName;
fEncodeParam.format = format;
avformat_network_init(); // 初始化网络库
AVStream* audioStream = nullptr;
AVFormatContext* inputFormatContext = nullptr;
AVStream* videoStream = nullptr;
// 打开输入文件
if (avformat_open_input(&inputFormatContext, fDecodeParam.inputFile.c_str(), nullptr, nullptr) != 0) {
std::cout << "无法打开输入文件" << std::endl;
return false;
}
ON_SCOPE_EXIT{ avformat_close_input(&inputFormatContext); };
// 获取流信息
if (avformat_find_stream_info(inputFormatContext, nullptr) < 0) {
std::cout << "无法获取输入文件流信息" << std::endl;
return false;
}
// 查找视频流和音频流索引
int videoStreamIndex = -1;
int audioStreamIndex = -1;
for (int i = 0; i < inputFormatContext->nb_streams; i++) {
if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
}
else if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
audioStreamIndex = i;
}
}
if (videoStreamIndex == -1 || audioStreamIndex == -1) {
std::cout << "没有找到视频流" << std::endl;
return false;
}
// 获取音频流,共享输入音频参数给音频编码器
audioStream = inputFormatContext->streams[audioStreamIndex];
videoStream = inputFormatContext->streams[videoStreamIndex];
fDecodeParam.inputFormatContext = inputFormatContext;
fDecodeParam.audioStream = audioStream;
fDecodeParam.videoStreamIndex = videoStreamIndex;
fDecodeParam.audioStreamIndex = audioStreamIndex;
fDecodeParam.videoStream = videoStream;
fEncodeParam.audioStream = audioStream;
fEncodeParam.videoStream = videoStream;
//解码线程
std::thread decodeThr(&MyTranscoderImpl::decodeThread1,this);
//编码线程
std::thread encodeThr(&MyTranscoderImpl::encodeThread1,this);
decodeThr.join();
encodeThr.join();
return true;
}
//解码线程
void MyTranscoderImpl::decodeThread1() {
AVCodecContext* videoCodecContext = nullptr;
AVCodecContext* audioCodecContext = nullptr;
// 分配帧对象
AVFrame* videoFrame = av_frame_alloc();
AVFrame* audioFrame = av_frame_alloc();
AVPacket* inputPacket = av_packet_alloc();
ON_SCOPE_EXIT{ av_frame_free(&videoFrame); };
ON_SCOPE_EXIT{ av_frame_free(&audioFrame); };
ON_SCOPE_EXIT{ av_packet_free(&inputPacket); };
if (!videoFrame || !audioFrame || !inputPacket) {
std::cout << "分配帧对象失败" << std::endl;
return;
}
// 获取视频解码器
const AVCodec* videoCodec = avcodec_find_decoder(fDecodeParam.videoStream->codecpar->codec_id);
if (!videoCodec) {
std::cout << "没有找到视频解码器" << std::endl;
return;
}
// 创建并打开视频解码器上下文
videoCodecContext = avcodec_alloc_context3(videoCodec);
if (!videoCodecContext) {
std::cout << "创建视频解码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&videoCodecContext); };
//视频流参数去填充上下文context
avcodec_parameters_to_context(videoCodecContext, fDecodeParam.videoStream->codecpar);
if (avcodec_open2(videoCodecContext, videoCodec, nullptr) < 0) {
std::cout << "打开视频解码器失败" << std::endl;
return;
}
// 获取音频编码器
const AVCodec* audioCodec = avcodec_find_decoder(fDecodeParam.audioStream->codecpar->codec_id);
if (!audioCodec) {
std::cout << "获取音频编码器失败" << std::endl;
return;
}
// 创建并打开音频解码器上下文
audioCodecContext = avcodec_alloc_context3(audioCodec);
if (!audioCodecContext) {
std::cout << "创建音频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&audioCodecContext); };
//音频流参数填充上下文
avcodec_parameters_to_context(audioCodecContext, fDecodeParam.audioStream->codecpar);
if (avcodec_open2(audioCodecContext, audioCodec, nullptr) < 0) {
std::cout << "打开音频编码器失败" << std::endl;
return;
}
//打印输入信息
av_dump_format(fDecodeParam.inputFormatContext, 0, fDecodeParam.inputFile.c_str(), 0);
//解码
while (av_read_frame(fDecodeParam.inputFormatContext, inputPacket) >= 0) {
if (inputPacket->stream_index == fDecodeParam.videoStreamIndex) {
fRet = avcodec_send_packet(videoCodecContext, inputPacket);
if (fRet < 0) {
break;
}
while (fRet >= 0) {
fRet = avcodec_receive_frame(videoCodecContext, videoFrame);
if (fRet == AVERROR(EAGAIN) || fRet == AVERROR_EOF) {
break;
}
else if (fRet < 0) {
std::cout << "视频解码 ret 异常" << std::endl;
return;
}
//传输帧到队列中,新建一个AVFrame_变量,避免每帧使用相同的地址
videoFrame->quality = 1;//音视频标志
AVFrame* videoFrame_ = av_frame_clone(videoFrame);
//如果帧队列大于等于50就等待唤醒
unique_lock<mutex> lock2(fMtx);
while (fFrameQueue.size() >= 50)
fCv.wait(lock2);
//将帧推入队列
fFrameQueue.push(videoFrame_);
//推入队列后,唤醒编码线程
fCv.notify_one();
break;
}
av_packet_unref(inputPacket);
}
else if (inputPacket->stream_index == fDecodeParam.audioStreamIndex) {
// 音频流处理
fRet = avcodec_send_packet(audioCodecContext, inputPacket);
if (fRet < 0) {
break;
}
while (fRet >= 0) {
fRet = avcodec_receive_frame(audioCodecContext, audioFrame);
if (fRet == AVERROR(EAGAIN) || fRet == AVERROR_EOF) {
break;
}
else if (fRet < 0) {
std::cout << "音频解码 ret 异常" << std::endl;
return;
}
//传输帧到队列中,新建一个AVFrame变量
AVFrame* audioFrame_ = av_frame_clone(audioFrame);
//如果帧队列大于等于50就等待唤醒
unique_lock<mutex> lock2(fMtx);
while (fFrameQueue.size() >= 50)
fCv.wait(lock2);
fFrameQueue.push(audioFrame_);
//唤醒编码线程
fCv.notify_one();
break;
}
av_packet_unref(inputPacket);
}
}
//解码完成后,唤醒最后一次
fCv.notify_one();
//设置结束全局变量,通知编码线程结束
fMtx.lock();
fEnd = true;
fMtx.unlock();
}
//编码线程
void MyTranscoderImpl::encodeThread1()
{
AVFormatContext* outputFormatContext = nullptr;
SwsContext* swsContext = nullptr;
AVCodecID videoCodecId;
AVCodecID audioCodecId;
AVPacket* videoOutputPacket = av_packet_alloc();
AVPacket* audioOutputPacket = av_packet_alloc();
ON_SCOPE_EXIT{ av_packet_free(&videoOutputPacket); };
ON_SCOPE_EXIT{ av_packet_free(&audioOutputPacket); };
if (!videoOutputPacket || !audioOutputPacket) {
std::cout << "分配帧对象失败" << std::endl;
return;
}
{ //编解码器控制
if (fEncodeParam.format == "avi")
{
videoCodecId = AV_CODEC_ID_MPEG2VIDEO;
audioCodecId = AV_CODEC_ID_PCM_S16LE;
}
else if (fEncodeParam.format == "mp4")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_AAC;
}
else if (fEncodeParam.format == "wmv")
{
videoCodecId = AV_CODEC_ID_MSMPEG4V3;
audioCodecId = AV_CODEC_ID_WMAV2;
}
else if (fEncodeParam.format == "mkv")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_MP3;
}
else if (fEncodeParam.format == "flv")
{
videoCodecId = AV_CODEC_ID_H264;
audioCodecId = AV_CODEC_ID_AAC;
}
else {
std::cout << "不支持转换为这种格式" << std::endl;
return;
}
}
// 创建输出文件的上下文
avformat_alloc_output_context2(&outputFormatContext, nullptr, nullptr, fEncodeParam.outputFileName.c_str());
if (!outputFormatContext) {
std::cout << "创建输出文件的上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avformat_free_context(outputFormatContext); };
// 添加视频流到输出上下文
AVStream* outVideoStream = avformat_new_stream(outputFormatContext, nullptr);
if (!outVideoStream) {
std::cout << "添加视频流到输出文件失败" << std::endl;
return;
}
outVideoStream->id = outputFormatContext->nb_streams - 1;
avcodec_parameters_copy(outVideoStream->codecpar, fEncodeParam.videoStream->codecpar);
outVideoStream->codecpar->codec_tag = 0;
// 设置视频编码器
const AVCodec* outVideoCodec = avcodec_find_encoder(videoCodecId);
if (!outVideoCodec) {
std::cout << "设置视频编码器失败" << std::endl;
return;
}
AVCodecContext* outVideoCodecContext = avcodec_alloc_context3(outVideoCodec);
if (!outVideoCodecContext) {
std::cout << "设置视频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&outVideoCodecContext); };
//视频编码器参数设置
{
//avcodec_parameters_to_context(outVideoCodecContext, outVideoStream->codecpar);
outVideoCodecContext->codec_id = videoCodecId;
outVideoCodecContext->time_base.den = 25;
outVideoCodecContext->time_base.num = 1;
outVideoCodecContext->gop_size = 13;
outVideoCodecContext->bit_rate = 8000000;
outVideoCodecContext->refs = 0;
outVideoCodecContext->max_b_frames = 10;
outVideoCodecContext->width = 1920;
outVideoCodecContext->height = 1080;
outVideoCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
}
//从输出上下文中复制参数到输出流
avcodec_parameters_from_context(outVideoStream->codecpar, outVideoCodecContext);
// 打开视频编码器
if (avcodec_open2(outVideoCodecContext, outVideoCodec, nullptr) < 0) {
std::cout << "无法打开视频编码器" << std::endl;
return;
}
// 添加音频流到输出文件
AVStream* outAudioStream = avformat_new_stream(outputFormatContext, nullptr);
if (!outAudioStream) {
std::cout << "添加音频流到输出文件失败" << std::endl;
return;
}
outAudioStream->id = outputFormatContext->nb_streams - 1;
//输出音频流参数复制
avcodec_parameters_copy(outAudioStream->codecpar, fEncodeParam.audioStream->codecpar);
outAudioStream->time_base.den = 11025;
outAudioStream->time_base.num = 256;
outAudioStream->codecpar->bit_rate = 320018;
outAudioStream->codecpar->profile = 1;
outAudioStream->codecpar->sample_rate = 44100;
outAudioStream->codecpar->frame_size = 1024;
av_channel_layout_default(&outAudioStream->codecpar->ch_layout, 2);
//outAudioStream->codecpar->ch_layout.nb_channels = 2;
outAudioStream->codecpar->codec_tag = 0;
//outAudioStream->codecpar->ch_layout.order = AV_CHANNEL_ORDER_NATIVE;
//outAudioStream->codecpar->ch_layout.u.mask = 0x03;
//outAudioStream->codecpar->channels = 1;
// 设置音频编码器
const AVCodec* outAudioCodec = avcodec_find_encoder(audioCodecId);
if (!outAudioCodec) {
std::cout << "设置音频编码器失败" << std::endl;
return;
}
AVCodecContext* outAudioCodecContext = avcodec_alloc_context3(outAudioCodec);
if (!outAudioCodecContext) {
std::cout << "设置音频编码器上下文失败" << std::endl;
return;
}
ON_SCOPE_EXIT{ avcodec_free_context(&outAudioCodecContext); };
//音频编码器参数
avcodec_parameters_to_context(outAudioCodecContext, outAudioStream->codecpar);
outAudioCodecContext->codec_id = audioCodecId;
outAudioCodecContext->time_base = fEncodeParam.audioStream->time_base;
//outAudioCodecContext->time_base.den = 51111100;
//outAudioCodecContext->time_base.num = 1;
//outAudioCodecContext->sample_rate = 43110;
outAudioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
//av_channel_layout_default(&outAudioCodecContext->ch_layout, 2);
avcodec_parameters_from_context(outAudioStream->codecpar, outAudioCodecContext);
if (fEncodeParam.format == "flv")
{
outAudioCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
av_channel_layout_default(&outAudioCodecContext->ch_layout, 3);
}
// 打开音频编码器
if (avcodec_open2(outAudioCodecContext, outAudioCodec, nullptr) < 0) {
std::cout << "无法打开音频编码器" << std::endl;
return;
}
// 打开输出文件
if (!(outputFormatContext->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&outputFormatContext->pb, fEncodeParam.outputFileName.c_str(), AVIO_FLAG_WRITE) < 0) {
std::cout << "无法打开输出文件" << std::endl;
return;
}
}
// 写入输出文件头
if (avformat_write_header(outputFormatContext, nullptr) < 0) {
std::cout << "无法写入输出文件头" << std::endl;
return;
}
//打印输出相关信息
av_dump_format(outputFormatContext, 0, fEncodeParam.outputFileName.c_str(), 1);
int nVideoCount = 0;
int nAudioCount = 0;
bool queueSizeLess = 0;
bool queueIsEmpty = 0;
//开始编码
while (1)
{
//等待解码唤醒
unique_lock<mutex> lock1(fMtx);
while (fFrameQueue.empty())
fCv.wait(lock1);
AVFrame* frame = fFrameQueue.front();
fFrameQueue.pop();
lock1.unlock();
//编码
{
if (frame->quality == 1) {
// 编码视频帧
frame->pts = (int64_t)(40 * (nVideoCount) / av_q2d(outVideoCodecContext->time_base) / 1000.0);//时间
nVideoCount++;
fRet = avcodec_send_frame(outVideoCodecContext, frame);
if (fRet < 0) {
break;
}
while (fRet >= 0) {
fRet = avcodec_receive_packet(outVideoCodecContext, videoOutputPacket);
if (fRet == AVERROR(EAGAIN) || fRet == AVERROR_EOF) {
break;
}
else if (fRet < 0) {
std::cout << "视频编码 ret 异常" << std::endl;
return;
}
av_packet_rescale_ts(videoOutputPacket, outVideoCodecContext->time_base, outVideoStream->time_base);
videoOutputPacket->stream_index = outVideoStream->index;
// 写入视频帧到输出文件
fRet = av_interleaved_write_frame(outputFormatContext, videoOutputPacket);
if (fRet < 0) {
break;
}
}
}
else {
// 编码音频帧
//Frame->pts = (int64_t)( (nAudioCount) / av_q2d(outAudioCodecContext->time_base) / 44100.0);//时间
frame->pts = nAudioCount * 1024;
nAudioCount++;
fRet = avcodec_send_frame(outAudioCodecContext, frame);
if (fRet < 0) {
break;
}
while (fRet >= 0) {
fRet = avcodec_receive_packet(outAudioCodecContext, audioOutputPacket);
if (fRet == AVERROR(EAGAIN) || fRet == AVERROR_EOF) {
break;
}
else if (fRet < 0) {
std::cout << "音频编码 ret 异常" << std::endl;
return;
}
av_packet_rescale_ts(audioOutputPacket, outAudioCodecContext->time_base, outAudioStream->time_base);
audioOutputPacket->stream_index = outAudioStream->index;
// 写入音频帧到输出文件
fRet = av_interleaved_write_frame(outputFormatContext, audioOutputPacket);
if (fRet < 0) {
break;
}
}
}
//释放每次新建的AVFrame
av_frame_free(&frame);
//如果队列帧数量已经少于50就唤醒解码继续工作
fMtx.lock();
queueSizeLess = fFrameQueue.size() < 50 ? 1 : 0;
queueIsEmpty = fFrameQueue.empty() ? 1 : 0;
fMtx.unlock();
if (queueSizeLess)
fCv.notify_one();
}
//结束整个编码线程
if (fEnd && queueIsEmpty) { break; }
}
// 写入输出文件尾部
av_write_trailer(outputFormatContext);
}
四、生成库
右击我们的项目名,再点击生成,就OK了,在下面这个目录会得到下面这四个文件。
五、其他人引用
其他人用我们的代码只需要头文件和lib库文件就可以了,新建一个项目模拟一下别人使用我们的代码。头文件比较简单直接把.h文件下载好然后include进来进行。
#include <iostream>
#include "../include/MyTranscoder.h"
int main()
{
clock_t start, end;
start = clock();
MyTranscoder transCoder;
if (!transCoder.transCode()) {
std::cout << "Failed to convert!" << std::endl;
return -1;
}
std::cout << "Conversion complete!" << std::endl;
end = clock();
std::cout << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << std::endl;
return 0;
}
库文件需要简单配置一下
属性--链接器--常规--附加库目录:然后把刚刚那个lib文件的路径放上去
属性--链接器--输入--附加依赖项:把MyTranscoder.lib库丢进去
这样就差不多OK了,初学者,记录记录学习过程。