原文:https://gocn.io/article/322
在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100
。而在AAC中有16种采样率96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
(还有4个是保留的)。也就是说,1在FLV中标识11025Hz,而在AAC中表示的是88200Hz。如何实现这个转换呢?
C++当然先得定义枚举:
enum SrsAudioSampleRate
{
SrsAudioSampleRate5512 = 0,
SrsAudioSampleRate11025,
SrsAudioSampleRate22050,
SrsAudioSampleRate44100,
SrsAudioSampleRateForbidden,
};
C++当然是用函数了:
SrsAudioSampleRate aac_to_flv(int v) {
if (v >= 0 && v <=5) {
return SrsAudioSampleRate44100;
} else if (v >=6 && v <= 8) {
return SrsAudioSampleRate22050;
} else if (v >= 9 && v <= 11) {
return SrsAudioSampleRate11025;
} else if (v == 12) {
return SrsAudioSampleRate5512;
} else {
return SrsAudioSampleRateForbidden;
}
}
看起来还是挺简单的。慢着,还有的时候需要打印出采样率来,所以还得搞个函数:
string srs_audio_sample_rate2str(SrsAudioSampleRate v)
{
switch (v) {
case SrsAudioSampleRate5512: return "5512";
case SrsAudioSampleRate11025: return "11025";
case SrsAudioSampleRate22050: return "22050";
case SrsAudioSampleRate44100: return "44100";
default: return "Forbidden";
}
}
拿到一个AAC的采样率,然后转换成FLV的,并打印出来,是这么使用的:
// 从文件或者流中读取出AAC的采样率的值。
int samplingFrequencyIndex = ...;
// 转换成FLV的采样率。
SrsAudioSampleRate sampleRate = aac_to_flv(samplingFrequencyIndex);
// 转换成字符串格式。
string sSampleRate = srs_audio_sample_rate2str(sampleRate);
// 打印采样率。
printf("SampleRate=%d/%sHz\n", sampleRate, sSampleRate);
有什么麻烦的呢?
还能不能愉快的玩耍呢?用GOLANG吧!先看用法:
var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n", sampleRate, sampleRate)
就是这么简单(此处应该有掌声)~
其实实现起来也非常自然:
type AudioSamplingRate uint8
const (
AudioSamplingRate5kHz AudioSamplingRate = iota // 0 = 5.5 kHz
AudioSamplingRate11kHz // 1 = 11 kHz
AudioSamplingRate22kHz // 2 = 22 kHz
AudioSamplingRate44kHz // 3 = 44 kHz
AudioSamplingRateForbidden
)
func (v AudioSamplingRate) String() string {
switch v {
case AudioSamplingRate5kHz:
return "5.5kHz"
case AudioSamplingRate11kHz:
return "11kHz"
case AudioSamplingRate22kHz:
return "22kHz"
case AudioSamplingRate44kHz:
return "44kHz"
default:
return "Forbidden"
}
}
func (v *AudioSamplingRate) From(a int) {
switch a {
case 0, 1, 2, 3, 4, 5:
*v = AudioSamplingRate44kHz
case 6, 7, 8:
*v = AudioSamplingRate22kHz
case 9, 10, 11:
*v = AudioSamplingRate11kHz
case 12:
*v = AudioSamplingRate5kHz
default:
*v = AudioSamplingRateForbidden
}
}
Remark: 代码参考commit.
有几个地方非常不同:
好吧,这只是几个小的改进,虽然用起来很方便。来看看在AMF0中基本类型的妙用,AMF0是一种传输格式,和JSON很像,不过JSON是文本的,而AMF0是字节的,都是用来在网络中传输对象的。因此,AMF0定义了几个基本的类型:String, Number, Boolean, Object,其中Object的属性定义为String的属性名和值,值可以是其他的类型。
先看看C++的实现,首先定义一个AMF0Any对象,可以转换成具体的String或者Object等对象:
class SrsAmf0Any {
// 提供转换的函数,获取实际的值。
virtual std::string to_str();
virtual bool to_boolean();
virtual double to_number();
virtual SrsAmf0Object* to_object();
// 当然还得提供判断的函数,得知道是什么类型才能转。
virtual bool is_string();
virtual bool is_boolean();
virtual bool is_number();
virtual bool is_object();
// 提供创建基本类型的函数。
static SrsAmf0Any* str(const char* value = NULL);
static SrsAmf0Any* boolean(bool value = false);
static SrsAmf0Any* number(double value = 0.0);
static SrsAmf0Object* object();
};
在实现时,String和Number等基本类型可以隐藏起来(在cpp中实现):
namespace _srs_internal {
class SrsAmf0String : public SrsAmf0Any {
public:
std::string value;
// 当然它必须实现编码和解码的函数。
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
};
}
AMF0Object当然得暴露出来的:
class SrsAmf0Object : public SrsAmf0Any {
public:
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
// 提供设置和读取属性的方法。
virtual void set(std::string key, SrsAmf0Any* value);
virtual SrsAmf0Any* get_property(std::string name);
};
用起来是这样:
// 设置Object的属性,并发送给服务器。
SrsConnectAppPacket* pkt = NULL;
pkt->command_object->set("app", SrsAmf0Any::str(app.c_str()));
pkt->command_object->set("tcUrl", SrsAmf0Any::str(tcUrl.c_str()));
// 读取服务器的响应,取出服务器的IP等信息。
SrsConnectAppResPacket* pkt = NULL;
SrsAmf0Any* data = pkt->info->get_property("data");
if (si && data && data->is_object()) {
SrsAmf0Object* obj = data->to_objet();
SrsAmf0Any* prop = obj->get_property("srs_server_ip");
if (prop && prop->is_string()) {
printf("Server IP: %s\n", prop->to_str().c_str());
}
prop = obj->get_property("srs_pid");
if (prop && prop->is_number()) {
printf("Server PID: %d\n, prop->to_number());
}
}
看起来巨繁琐吧?快用GOLANG,如果换成GOLANG,可以用基本类型定义AMF0的基本类型,这样使用起来是这样:
pkt := or.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl", amf0.NewString(tcUrl))
pkt.CommandObject.Set("app", amf0.NewString(app))
var res *or.ConnectAppResPacket
if data, ok := res.Args.Get("data").(*amf0.Object); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
fmt.Printf("Server IP: %s\n", string(*data))
}
if data, ok := data.Get("srs_pid").(*amf0.Number); ok {
fmt.Printf("Server PID: %d\n, int(*data))
}
}
区别在于:
爱生活,爱够浪(此处可以响起掌声了)~