Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

+ Test script for rtmp audio

  • Loading branch information...
commit 6ffee09ca661b3cfdbb0d0d8edf3b562287c131d 1 parent 44b4e0a
ru authored
View
7 etc/scripts/test_rtmpd_session.nut
@@ -13,6 +13,8 @@ class CGlobal
codec = "H264";
// codec = "VP60";
+ acodec = "AAC";
+
count = 0;
format = 0;
@@ -169,8 +171,8 @@ function SendVideo() : ( _g )
if ( !data.getUsed() )
return 1;
-//if ( !_g.count++ )
-// _self.echo( "--- VIDEO FRAME ---\n" + data.AsciiHexStr( 16, 32 ) );
+// if ( !_g.count++ )
+// _self.echo( "--- VIDEO FRAME ---\n" + data.AsciiHexStr( 16, 32 ) );
// Append packet fragment
_g.frame.Append( data );
@@ -439,6 +441,7 @@ function ProcessCommands() : ( _g )
local p = CSqMulti();
p[ "codec" ] <- _g.codec;
+ p[ "acodec" ] <- _g.acodec;
p[ "type" ] <- "flv";
p[ "w" ] <- "320";
p[ "h" ] <- "240";
View
81 etc/scripts/test_videostream.thread.nut
@@ -19,6 +19,14 @@ class CGlobal
share = 0;
frame = 0;
+ aenc = 0;
+ ashare = 0;
+ abuf = 0;
+ aframe = 0;
+ audio_ref = 0;
+ audio_channels = 1;
+ audio_samplerate = 44100;
+
ft = 0;
font = 0;
@@ -163,6 +171,26 @@ function StartStream( params ) : ( _g )
// Do we need a container?
if ( p[ "type" ].len() )
{
+ // Do we need to include audio?
+ local acodec = "", acid = 0;
+ if ( p[ "acodec" ].len() )
+ {
+ // Create encoder
+ _g.aenc = CFfAudioEncoder();
+ _g.aenc.setTimeBase( _g.fps );
+ acodec = p[ "acodec" ].str();
+ acid = CFfAudioDecoder().LookupCodecId( acodec );
+ if ( !acid || !_g.aenc.Create( acid, ::_self.tFloat, _g.audio_channels,
+ _g.audio_samplerate, 0, CSqMulti( "cmp=-2") ) )
+ { _g.as = 0; _g.aenc = 0;
+ _self.echo( "Failed to create audio encoder : " + acodec );
+ } // end if
+
+ else
+ _g.abuf = CSqBinary(), _g.aframe = CSqBinary();
+
+ } // end if
+
// Video container
_g.vid = CFfContainer();
local url = "memshare://" + p[ "sid" ].str()
@@ -186,6 +214,11 @@ function StartStream( params ) : ( _g )
return 0
} // end if
+ // Attempt to add audio stream
+ if ( acid )
+ if ( 0 > _g.vid.AddAudioStream( acid, ::_self.tFloat, _g.audio_channels, _g.audio_samplerate, 0 ) )
+ _self.echo( "Failed to add audio stream : " + acodec );
+
// Initilalize write
if ( !_g.vid.InitWrite() )
{ ::_self.echo( "Failed to initialize video container : " + p[ "type" ].str() );
@@ -279,10 +312,54 @@ function Run() : ( _g )
// Write to container
if ( _g.vid )
{
+ // Audio?
+ if ( _g.aenc )
+ {
+ // Calculate a buffer size and allocate
+ local bsize = _g.audio_samplerate / ( _g.fps ? _g.fps : 15 );
+ if ( !_g.abuf.Size() && !_g.abuf.AllocateFLOAT( bsize ) )
+ _g.aenc = 0;
+
+ if ( _g.abuf.Size() )
+ {
+ // Create sine wave
+ _g.abuf.setUsed( bsize * _g.abuf.sizeFLOAT() );
+
+ // Create sine wave
+ local div = _g.audio_samplerate.tofloat() / 440., pi2 = 3.14159 * 2.;
+ for( local i = 0; i < bsize; i++ )
+ _g.abuf.setFLOAT( i, sin( _g.audio_ref++ / div * pi2 ) );
+
+ // Buffer the audio frame
+ _g.aenc.BufferData( _g.abuf );
+
+ // Encode audio data
+ local inf = CSqMulti();
+ while ( _g.aenc.Encode( CSqBinary(), _g.aframe, inf ) )
+ {
+// _self.echo( "AUDIO = " + _g.aframe.getUsed() + ", pts = " + _g.aenc.getPts() );
+
+ // Write audio data
+ if ( _g.aframe.getUsed() )
+ {
+// if ( !_g.vid.WriteAudioFrame( _g.aframe, _g.enc.calcPts(), _g.enc.calcPts(), CSqMulti() ) )
+// if ( !_g.vid.WriteAudioFrame( _g.aframe, _g.aenc.getPts(), _g.aenc.getPts(), CSqMulti() ) )
+ if ( !_g.vid.WriteAudioFrame( _g.aframe, 0, 0, CSqMulti() ) )
+ _self.echo( "!!! Error writing audio frame to video stream" );
+ _g.aframe.setUsed( 0 );
+
+ } // end if
+
+ } // end while
+
+ } // end else
+
+ } // end if
+
// Attempt to write the frame
// if ( !_g.vid.WriteVideoFrame( _g.frame, _g.enc.calcPts(), _g.enc.calcPts(), inf ) )
- if ( !_g.vid.WriteVideoFrame( _g.frame, inf[ "pts" ].toint(), inf[ "pts" ].toint(), inf ) )
-// if ( !_g.vid.WriteVideoFrame( _g.frame, -1, -1, inf ) )
+// if ( !_g.vid.WriteVideoFrame( _g.frame, inf[ "pts" ].toint(), inf[ "pts" ].toint(), inf ) )
+ if ( !_g.vid.WriteVideoFrame( _g.frame, 0, 0, inf ) )
_g.quit = 1;
// Flush data buffers
View
4 lib/sqbind/import/sq_binary_share.cpp
@@ -405,7 +405,7 @@ sqbind::CSqBinary CSqBinaryShare::Read()
return sqbind::CSqBinary();
// Count a read
- m_cb.setUINT( 5, m_cb.getUINT( 5 ) + 1 );
+ m_cb.setUINT( 9, m_cb.getUINT( 9 ) + 1 );
return m_buf.getSub( s, e - s );
}
@@ -488,7 +488,7 @@ int CSqBinaryShare::WritePtr( const void *pData, int nSize )
m_buf.Mem().MemCpy( &((oex::CBin::t_byte*)pData)[ l ], w ), i += w;
// Count a frame written
- m_cb.setUINT( 4, m_cb.getUINT( 4 ) + 1 );
+ m_cb.setUINT( 8, m_cb.getUINT( 8 ) + 1 );
// Loop pointer
if ( i >= nBufSize )
View
70 sqmod/sqmod_ffmpeg/ff_audio_encoder.cpp
@@ -21,9 +21,12 @@ SQBIND_REGISTER_CLASS_BEGIN( CFfAudioEncoder, CFfAudioEncoder )
SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, getCodecId )
SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, getFmtCnv )
SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, setFmtCnv )
+ SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, getTimeBase )
+ SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, setTimeBase )
+ SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, getFrame )
+ SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, setFrame )
// SQBIND_MEMBER_FUNCTION( CFfAudioEncoder, )
-
SQBIND_REGISTER_CLASS_END()
DECLARE_INSTANCE_TYPE( CFfAudioEncoder );
@@ -36,6 +39,8 @@ CFfAudioEncoder::CFfAudioEncoder()
{_STT();
m_nFmt = 0;
+ m_nFrame = 0;
+ m_nTimeBase = 0;
m_nCodecId = 0;
m_pCodec = oexNULL;
m_pCodecContext = oexNULL;
@@ -66,6 +71,7 @@ void CFfAudioEncoder::Destroy()
} // end if
m_nFmt = 0;
+ m_nFrame = 0;
m_nCodecId = 0;
m_pCodec = oexNULL;
m_pOutput = oexNULL;
@@ -120,6 +126,11 @@ int CFfAudioEncoder::Create( int x_nCodec, int x_nFmt, int x_nChannels, int x_nS
return 0;
} // end switch
+ // Get time base
+ oex::oexINT64 nTimeBase = m_nTimeBase;
+ if ( 0 >= nTimeBase )
+ nTimeBase = x_nSampleRate;
+
// Set codec parameters
m_pCodecContext->channels = x_nChannels;
m_pCodecContext->sample_rate = x_nSampleRate;
@@ -128,11 +139,14 @@ int CFfAudioEncoder::Create( int x_nCodec, int x_nFmt, int x_nChannels, int x_nS
m_pCodecContext->bit_rate = m_pCodecContext->sample_rate * ( x_nFmt & 0xf ) * m_pCodecContext->channels * 8;
m_pCodecContext->channel_layout = ( 1 == x_nChannels ) ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
m_pCodecContext->time_base.num = 1;
- m_pCodecContext->time_base.den = x_nSampleRate;
+ m_pCodecContext->time_base.den = nTimeBase;
+ m_pCodecContext->strict_std_compliance = ( ( m && m->isset( oexT( "cmp" ) ) ) ? (*m)[ oexT( "cmp" ) ].toint() : 0 );
// Check profile for aac
if ( CODEC_ID_AAC == x_nCodec )
- m_pCodecContext->profile = FF_PROFILE_AAC_MAIN;
+// m_pCodecContext->profile = FF_PROFILE_AAC_MAIN;
+ m_pCodecContext->profile = FF_PROFILE_AAC_LOW,
+ m_pCodecContext->bit_rate = m_pCodecContext->sample_rate * ( x_nFmt & 0xf ) * m_pCodecContext->channels;
if ( m && m->isset( oexT( "quality" ) ) )
{
@@ -269,7 +283,8 @@ int CFfAudioEncoder::Encode( sqbind::CSqBinary *in, sqbind::CSqBinary *out, sqbi
uint8_t *pOut = (uint8_t*)out->_Ptr();
// While we have input data
- while ( nIn >= bs )
+ //while ( nIn >= bs )
+ if ( nIn >= bs )
{
// Ensure a reasonable output buffer
while ( ( nOut - nOutPtr ) < ( bs + FF_MIN_BUFFER_SIZE ) )
@@ -281,13 +296,57 @@ int CFfAudioEncoder::Encode( sqbind::CSqBinary *in, sqbind::CSqBinary *out, sqbi
return 0;
} // end if
+#if defined( DEVEL_USE_AVCODEC_ENCODE_AUDIO2 )
+
+// ############### 2
+
+ AVFrame *pDst = avcodec_alloc_frame();
+ if ( !pDst )
+ { oexERROR( nOut, oexT( "avcodec_alloc_frame() failed" ) );
+ return 0;
+ } // end if
+
+ // Get frame defaults
+ avcodec_get_frame_defaults( pDst );
+
+ pDst->pts = calcPts();
+ pDst->nb_samples = fs;
+ pDst->format = m_pCodecContext->sample_fmt;
+// pDst->channels = m_pCodecContext->channels;
+ pDst->sample_rate = m_pCodecContext->sample_rate;
+ pDst->channel_layout = m_pCodecContext->channel_layout;
+ pDst->type = 0;
+ void *data = &pOut[ nOutPtr ];
+ pDst->extended_data = (uint8_t**)&data;
+ pDst->linesize[ 0 ] = bs;
+
+// if ( 0 > avcodec_fill_audio_frame( pDst, m_pCodecContext->channels, m_pCodecContext->sample_fmt,
+// &pOut[ nOutPtr ], nOut - nOutPtr, 1 ) )
+// { oexERROR( nOut, oexT( "avcodec_fill_audio_frame() failed" ) );
+// return 0;
+// } // end if
+
// Encode a frame
+ int gop = 0;
+ int nBytes = avcodec_encode_audio2( m_pCodecContext, &m_pkt, pDst, &gop );
+ if ( 0 > nBytes )
+ { oexERROR( nBytes, oexT( "avcodec_encode_audio2() failed" ) );
+ return 0;
+ } // end if
+
+ av_free( pDst );
+
+// ############### 2
+#else
+
int nBytes = avcodec_encode_audio( m_pCodecContext, &pOut[ nOutPtr ], nOut - nOutPtr, (const short*)m_pkt.data );
if ( 0 > nBytes )
{ oexERROR( nBytes, oexT( "avcodec_encode_audio() failed" ) );
return 0;
} // end if
+#endif
+
// Add bytes
if ( 0 < nBytes )
nOutPtr += nBytes;
@@ -313,6 +372,9 @@ int CFfAudioEncoder::Encode( sqbind::CSqBinary *in, sqbind::CSqBinary *out, sqbi
} // end if
+ // Count a frame
+ m_nFrame++;
+
return nOutPtr;
}
View
34 sqmod/sqmod_ffmpeg/ff_audio_encoder.h
@@ -88,6 +88,22 @@ class CFfAudioEncoder
return m_pCodecContext->coded_frame->pts;
}
+ /// Calculates the PTS based on the current frame index
+ SQInteger calcPts()
+ {
+ // Sanity checks
+ if ( !m_pCodecContext )
+ return -1;
+
+ // +++ So this is probably just 'return m_nFrame'
+ oex::oexINT64 nFps = m_pCodecContext->time_base.den;
+ if ( 0 >= nFps )
+ return -1;
+
+ // Calculate pts
+ return m_nFrame * m_pCodecContext->time_base.den / ( m_pCodecContext->time_base.num * nFps );
+ }
+
/// Returns the Frame size
SQInteger getFrameSize()
{ if ( !m_pCodecContext && m_pCodecContext )
@@ -107,6 +123,18 @@ class CFfAudioEncoder
/// Set input data format
void setFmtCnv( int n ) { m_nCnv = n; }
+ /// Returns the current frame number
+ SQInteger getFrame() { return m_nFrame; }
+
+ /// Sets the current frame number
+ void setFrame( SQInteger n ) { m_nFrame = n; }
+
+ /// Sets the time base, if zero, defaults to frame rate
+ void setTimeBase( SQInteger n ) { m_nTimeBase = n; }
+
+ /// Returns the current time base
+ SQInteger getTimeBase() { return m_nTimeBase; }
+
/** @} */
private:
@@ -120,6 +148,12 @@ class CFfAudioEncoder
/// Codec ID
int m_nCodecId;
+ /// Time base, if zero, defaults to m_nFps
+ oex::oexINT64 m_nTimeBase;
+
+ /// Frame number
+ oex::oexINT64 m_nFrame;
+
/// Audio data buffer
sqbind::CSqBinary m_buf;
View
34 sqmod/sqmod_rtmpd/rtmpd_session.cpp
@@ -57,6 +57,7 @@ CRtmpdSession::CRtmpdSession()
oexZero( m_packet );
m_nPacketReady = 0;
m_nNonBlockingMode = 0;
+ m_nTs = 0;
}
void CRtmpdSession::Destroy()
@@ -70,6 +71,7 @@ void CRtmpdSession::Destroy()
oexZero( m_packet );
m_nPacketReady = 0;
m_nNonBlockingMode = 0;
+ m_nTs = 0;
}
SQInteger CRtmpdSession::getLibVersion()
@@ -194,6 +196,10 @@ int CRtmpdSession::Init( sqbind::CSqSocket *pSocket )
// Give the rtmpd object control of the socket handle
m_session.m_sb.sb_socket = oexPtrToInt( pSocket->Ptr()->Detach() );
+ // Disable Nagle's algorithm
+ int on = 1;
+ setsockopt( m_session.m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof( on ) );
+
// Attempt handshake
if ( !RTMP_Serve( &m_session ) )
{ setLastErrorStr( "RTMP handshake failed" );
@@ -774,9 +780,19 @@ int CRtmpdSession::SendPacket2( int format, int csi, int type, int ext, sqbind::
m_packet.m_nChannel = csi;
m_packet.m_headerType = format;
m_packet.m_packetType = type;
- m_packet.m_nTimeStamp = 0;
m_packet.m_nInfoField2 = ext;
- m_packet.m_hasAbsTimestamp = 0;
+/*
+ // Audio
+ if ( 8 == type )
+ m_packet.m_nTimeStamp = m_nTs++;
+ // Video
+ else if ( 9 == type )
+ m_packet.m_nTimeStamp = m_nTs;
+ else
+*/ m_packet.m_nTimeStamp = 0;
+
+ // Timestamps?
+ m_packet.m_hasAbsTimestamp = ( 0 < m_nTs ) ? 1 : 0;
sqbind::CSqBinary body;
if ( !body.Allocate( RTMP_MAX_HEADER_SIZE + 1024 ) )
@@ -829,9 +845,19 @@ int CRtmpdSession::SendPacketBin( int format, int csi, int type, int ext, sqbind
m_packet.m_nChannel = csi;
m_packet.m_headerType = format;
m_packet.m_packetType = type;
- m_packet.m_nTimeStamp = 0;
m_packet.m_nInfoField2 = ext;
- m_packet.m_hasAbsTimestamp = 0;
+/*
+ // Audio
+ if ( 8 == type )
+ m_packet.m_nTimeStamp = m_nTs++;
+ // Video
+ else if ( 9 == type )
+ m_packet.m_nTimeStamp = m_nTs;
+ else
+*/ m_packet.m_nTimeStamp = 0;
+
+ // Timestamps?
+ m_packet.m_hasAbsTimestamp = ( 0 < m_nTs ) ? 1 : 0;
sqbind::CSqBinary body;
if ( !body.Allocate( RTMP_MAX_HEADER_SIZE + b->getUsed() + 128 ) )
View
7 sqmod/sqmod_rtmpd/rtmpd_session.h
@@ -39,7 +39,7 @@ class CRtmpdSession
/// Destroy connection
void Destroy();
-
+
/// Initializes the RTMP session on the specified socket
int Init( sqbind::CSqSocket *pSocket );
@@ -63,12 +63,10 @@ class CRtmpdSession
/// Read a packet message from the server
/**
-
Returns
< 0 - Client session is closed
= 0 - No packet or incomplete packet, call again later
> 0 - Packet received
-
*/
int ReadPacket();
@@ -136,5 +134,8 @@ class CRtmpdSession
/// Non-zero for non-blocking mode
int m_nNonBlockingMode;
+ /// Timestamp
+ long m_nTs;
+
};
Please sign in to comment.
Something went wrong with that request. Please try again.