mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
For #913, APP support complex error.
This commit is contained in:
parent
6eae93258a
commit
e2c1f58674
13 changed files with 423 additions and 536 deletions
|
@ -78,21 +78,16 @@ srs_error_t SrsRtpConn::listen()
|
|||
|
||||
srs_error_t SrsRtpConn::on_udp_packet(sockaddr_in* from, char* buf, int nb_buf)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
pprint->elapse();
|
||||
|
||||
if (true) {
|
||||
SrsBuffer stream;
|
||||
|
||||
if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "stream");
|
||||
}
|
||||
SrsBuffer stream(buf, nb_buf);
|
||||
|
||||
SrsRtpPacket pkt;
|
||||
if ((ret = pkt.decode(&stream)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "decode");
|
||||
if ((err = pkt.decode(&stream)) != srs_success) {
|
||||
return srs_error_wrap(err, "decode");
|
||||
}
|
||||
|
||||
if (pkt.chunked) {
|
||||
|
@ -125,8 +120,8 @@ srs_error_t SrsRtpConn::on_udp_packet(sockaddr_in* from, char* buf, int nb_buf)
|
|||
// always free it.
|
||||
SrsAutoFree(SrsRtpPacket, cache);
|
||||
|
||||
if ((ret = rtsp->on_rtp_packet(cache, stream_id)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "process rtp packet");
|
||||
if ((err = rtsp->on_rtp_packet(cache, stream_id)) != srs_success) {
|
||||
return srs_error_wrap(err, "process rtp packet");
|
||||
}
|
||||
|
||||
return err;
|
||||
|
@ -161,9 +156,9 @@ int64_t SrsRtspJitter::timestamp()
|
|||
return pts;
|
||||
}
|
||||
|
||||
int SrsRtspJitter::correct(int64_t& ts)
|
||||
srs_error_t SrsRtspJitter::correct(int64_t& ts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if (previous_timestamp == 0) {
|
||||
previous_timestamp = ts;
|
||||
|
@ -179,7 +174,7 @@ int SrsRtspJitter::correct(int64_t& ts)
|
|||
ts = pts + delta;
|
||||
pts = ts;
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
SrsRtspConn::SrsRtspConn(SrsRtspCaster* c, srs_netfd_t fd, std::string o)
|
||||
|
@ -246,7 +241,6 @@ srs_error_t SrsRtspConn::serve()
|
|||
|
||||
srs_error_t SrsRtspConn::do_cycle()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// retrieve ip of client.
|
||||
|
@ -260,8 +254,8 @@ srs_error_t SrsRtspConn::do_cycle()
|
|||
}
|
||||
|
||||
SrsRtspRequest* req = NULL;
|
||||
if ((ret = rtsp->recv_message(&req)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "recv message");
|
||||
if ((err = rtsp->recv_message(&req)) != srs_success) {
|
||||
return srs_error_wrap(err, "recv message");
|
||||
}
|
||||
SrsAutoFree(SrsRtspRequest, req);
|
||||
srs_info("rtsp: got rtsp request");
|
||||
|
@ -269,8 +263,8 @@ srs_error_t SrsRtspConn::do_cycle()
|
|||
if (req->is_options()) {
|
||||
SrsRtspOptionsResponse* res = new SrsRtspOptionsResponse((int)req->seq);
|
||||
res->session = session;
|
||||
if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "response option");
|
||||
if ((err = rtsp->send_message(res)) != srs_success) {
|
||||
return srs_error_wrap(err, "response option");
|
||||
}
|
||||
} else if (req->is_announce()) {
|
||||
if (rtsp_tcUrl.empty()) {
|
||||
|
@ -300,14 +294,14 @@ srs_error_t SrsRtspConn::do_cycle()
|
|||
|
||||
SrsRtspResponse* res = new SrsRtspResponse((int)req->seq);
|
||||
res->session = session;
|
||||
if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "response announce");
|
||||
if ((err = rtsp->send_message(res)) != srs_success) {
|
||||
return srs_error_wrap(err, "response announce");
|
||||
}
|
||||
} else if (req->is_setup()) {
|
||||
srs_assert(req->transport);
|
||||
int lpm = 0;
|
||||
if ((ret = caster->alloc_port(&lpm)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "alloc port");
|
||||
if ((err = caster->alloc_port(&lpm)) != srs_success) {
|
||||
return srs_error_wrap(err, "alloc port");
|
||||
}
|
||||
|
||||
SrsRtpConn* rtp = NULL;
|
||||
|
@ -338,14 +332,14 @@ srs_error_t SrsRtspConn::do_cycle()
|
|||
res->local_port_min = lpm;
|
||||
res->local_port_max = lpm + 1;
|
||||
res->session = session;
|
||||
if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "response setup");
|
||||
if ((err = rtsp->send_message(res)) != srs_success) {
|
||||
return srs_error_wrap(err, "response setup");
|
||||
}
|
||||
} else if (req->is_record()) {
|
||||
SrsRtspResponse* res = new SrsRtspResponse((int)req->seq);
|
||||
res->session = session;
|
||||
if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) {
|
||||
return srs_error_new(ret, "response record");
|
||||
if ((err = rtsp->send_message(res)) != srs_success) {
|
||||
return srs_error_wrap(err, "response record");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -353,21 +347,20 @@ srs_error_t SrsRtspConn::do_cycle()
|
|||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::on_rtp_packet(SrsRtpPacket* pkt, int stream_id)
|
||||
srs_error_t SrsRtspConn::on_rtp_packet(SrsRtpPacket* pkt, int stream_id)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// ensure rtmp connected.
|
||||
if ((ret = connect()) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = connect()) != srs_success) {
|
||||
return srs_error_wrap(err, "connect");
|
||||
}
|
||||
|
||||
if (stream_id == video_id) {
|
||||
// rtsp tbn is ts tbn.
|
||||
int64_t pts = pkt->timestamp;
|
||||
if ((ret = vjitter->correct(pts)) != ERROR_SUCCESS) {
|
||||
srs_error("rtsp: correct by jitter failed. ret=%d", ret);
|
||||
return ret;
|
||||
if ((err = vjitter->correct(pts)) != srs_success) {
|
||||
return srs_error_wrap(err, "jitter");
|
||||
}
|
||||
|
||||
// TODO: FIXME: set dts to pts, please finger out the right dts.
|
||||
|
@ -377,15 +370,14 @@ int SrsRtspConn::on_rtp_packet(SrsRtpPacket* pkt, int stream_id)
|
|||
} else {
|
||||
// rtsp tbn is ts tbn.
|
||||
int64_t pts = pkt->timestamp;
|
||||
if ((ret = ajitter->correct(pts)) != ERROR_SUCCESS) {
|
||||
srs_error("rtsp: correct by jitter failed. ret=%d", ret);
|
||||
return ret;
|
||||
if ((err = ajitter->correct(pts)) != srs_success) {
|
||||
return srs_error_wrap(err, "jitter");
|
||||
}
|
||||
|
||||
return on_rtp_audio(pkt, pts);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
srs_error_t SrsRtspConn::cycle()
|
||||
|
@ -414,31 +406,31 @@ srs_error_t SrsRtspConn::cycle()
|
|||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::on_rtp_video(SrsRtpPacket* pkt, int64_t dts, int64_t pts)
|
||||
srs_error_t SrsRtspConn::on_rtp_video(SrsRtpPacket* pkt, int64_t dts, int64_t pts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if ((ret = kickoff_audio_cache(pkt, dts)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = kickoff_audio_cache(pkt, dts)) != srs_success) {
|
||||
return srs_error_wrap(err, "kickoff audio cache");
|
||||
}
|
||||
|
||||
char* bytes = pkt->payload->bytes();
|
||||
int length = pkt->payload->length();
|
||||
uint32_t fdts = (uint32_t)(dts / 90);
|
||||
uint32_t fpts = (uint32_t)(pts / 90);
|
||||
if ((ret = write_h264_ipb_frame(bytes, length, fdts, fpts)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = write_h264_ipb_frame(bytes, length, fdts, fpts)) != srs_success) {
|
||||
return srs_error_wrap(err, "write ibp frame");
|
||||
}
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::on_rtp_audio(SrsRtpPacket* pkt, int64_t dts)
|
||||
srs_error_t SrsRtspConn::on_rtp_audio(SrsRtpPacket* pkt, int64_t dts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if ((ret = kickoff_audio_cache(pkt, dts)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = kickoff_audio_cache(pkt, dts)) != srs_success) {
|
||||
return srs_error_wrap(err, "kickoff audio cache");
|
||||
}
|
||||
|
||||
// cache current audio to kickoff.
|
||||
|
@ -449,16 +441,16 @@ int SrsRtspConn::on_rtp_audio(SrsRtpPacket* pkt, int64_t dts)
|
|||
pkt->audio = NULL;
|
||||
pkt->payload = NULL;
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts)
|
||||
srs_error_t SrsRtspConn::kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// nothing to kick off.
|
||||
if (!acache->payload) {
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
if (dts - acache->dts > 0 && acache->audio->nb_samples > 0) {
|
||||
|
@ -468,8 +460,8 @@ int SrsRtspConn::kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts)
|
|||
int nb_frame = acache->audio->samples[i].size;
|
||||
int64_t timestamp = (acache->dts + delta * i) / 90;
|
||||
acodec->aac_packet_type = 1;
|
||||
if ((ret = write_audio_raw_frame(frame, nb_frame, acodec, (uint32_t)timestamp)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = write_audio_raw_frame(frame, nb_frame, acodec, (uint32_t)timestamp)) != srs_success) {
|
||||
return srs_error_wrap(err, "write audio raw frame");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -478,20 +470,19 @@ int SrsRtspConn::kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts)
|
|||
srs_freep(acache->audio);
|
||||
srs_freep(acache->payload);
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::write_sequence_header()
|
||||
srs_error_t SrsRtspConn::write_sequence_header()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// use the current dts.
|
||||
int64_t dts = vjitter->timestamp() / 90;
|
||||
|
||||
// send video sps/pps
|
||||
if ((ret = write_h264_sps_pps((uint32_t)dts, (uint32_t)dts)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = write_h264_sps_pps((uint32_t)dts, (uint32_t)dts)) != srs_success) {
|
||||
return srs_error_wrap(err, "write sps/pps");
|
||||
}
|
||||
|
||||
// generate audio sh by audio specific config.
|
||||
|
@ -502,10 +493,7 @@ int SrsRtspConn::write_sequence_header()
|
|||
SrsAutoFree(SrsFormat, format);
|
||||
|
||||
if ((err = format->on_aac_sequence_header((char*)sh.c_str(), (int)sh.length())) != srs_success) {
|
||||
// TODO: FIXME: Use error
|
||||
ret = srs_error_code(err);
|
||||
srs_freep(err);
|
||||
return ret;
|
||||
return srs_error_wrap(err, "on aac sequence header");
|
||||
}
|
||||
|
||||
SrsAudioCodecConfig* dec = format->acodec;
|
||||
|
@ -535,22 +523,22 @@ int SrsRtspConn::write_sequence_header()
|
|||
break;
|
||||
};
|
||||
|
||||
if ((ret = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), acodec, (uint32_t)dts)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), acodec, (uint32_t)dts)) != srs_success) {
|
||||
return srs_error_wrap(err, "write audio raw frame");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::write_h264_sps_pps(uint32_t dts, uint32_t pts)
|
||||
srs_error_t SrsRtspConn::write_h264_sps_pps(uint32_t dts, uint32_t pts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// h264 raw to h264 packet.
|
||||
std::string sh;
|
||||
if ((ret = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != srs_success) {
|
||||
return srs_error_wrap(err, "mux sequence header");
|
||||
}
|
||||
|
||||
// h264 packet to flv packet.
|
||||
|
@ -558,22 +546,22 @@ int SrsRtspConn::write_h264_sps_pps(uint32_t dts, uint32_t pts)
|
|||
int8_t avc_packet_type = SrsVideoAvcFrameTraitSequenceHeader;
|
||||
char* flv = NULL;
|
||||
int nb_flv = 0;
|
||||
if ((ret = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) {
|
||||
return srs_error_wrap(err, "mux avc to flv");
|
||||
}
|
||||
|
||||
// the timestamp in rtmp message header is dts.
|
||||
uint32_t timestamp = dts;
|
||||
if ((ret = rtmp_write_packet(SrsFrameTypeVideo, timestamp, flv, nb_flv)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = rtmp_write_packet(SrsFrameTypeVideo, timestamp, flv, nb_flv)) != srs_success) {
|
||||
return srs_error_wrap(err, "write packet");
|
||||
}
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts)
|
||||
srs_error_t SrsRtspConn::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// 5bits, 7.3.1 NAL unit syntax,
|
||||
// ISO_IEC_14496-10-AVC-2003.pdf, page 44.
|
||||
|
@ -587,15 +575,15 @@ int SrsRtspConn::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts,
|
|||
}
|
||||
|
||||
std::string ibp;
|
||||
if ((ret = avc->mux_ipb_frame(frame, frame_size, ibp)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = avc->mux_ipb_frame(frame, frame_size, ibp)) != srs_success) {
|
||||
return srs_error_wrap(err, "mux ibp frame");
|
||||
}
|
||||
|
||||
int8_t avc_packet_type = SrsVideoAvcFrameTraitNALU;
|
||||
char* flv = NULL;
|
||||
int nb_flv = 0;
|
||||
if ((ret = avc->mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = avc->mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) {
|
||||
return srs_error_wrap(err, "mux avc to flv");
|
||||
}
|
||||
|
||||
// the timestamp in rtmp message header is dts.
|
||||
|
@ -603,51 +591,50 @@ int SrsRtspConn::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts,
|
|||
return rtmp_write_packet(SrsFrameTypeVideo, timestamp, flv, nb_flv);
|
||||
}
|
||||
|
||||
int SrsRtspConn::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, uint32_t dts)
|
||||
srs_error_t SrsRtspConn::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, uint32_t dts)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
char* data = NULL;
|
||||
int size = 0;
|
||||
if ((ret = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != srs_success) {
|
||||
return srs_error_wrap(err, "mux aac to flv");
|
||||
}
|
||||
|
||||
return rtmp_write_packet(SrsFrameTypeAudio, dts, data, size);
|
||||
}
|
||||
|
||||
int SrsRtspConn::rtmp_write_packet(char type, uint32_t timestamp, char* data, int size)
|
||||
srs_error_t SrsRtspConn::rtmp_write_packet(char type, uint32_t timestamp, char* data, int size)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
if ((ret = connect()) != ERROR_SUCCESS) {
|
||||
return ret;
|
||||
if ((err = connect()) != srs_success) {
|
||||
return srs_error_wrap(err, "connect");
|
||||
}
|
||||
|
||||
SrsSharedPtrMessage* msg = NULL;
|
||||
|
||||
if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, sdk->sid(), &msg)) != ERROR_SUCCESS) {
|
||||
srs_error("rtsp: create shared ptr msg failed. ret=%d", ret);
|
||||
return ret;
|
||||
if ((err = srs_rtmp_create_msg(type, timestamp, data, size, sdk->sid(), &msg)) != srs_success) {
|
||||
return srs_error_wrap(err, "create message");
|
||||
}
|
||||
srs_assert(msg);
|
||||
|
||||
// send out encoded msg.
|
||||
if ((ret = sdk->send_and_free_message(msg)) != ERROR_SUCCESS) {
|
||||
if ((err = sdk->send_and_free_message(msg)) != srs_success) {
|
||||
close();
|
||||
return ret;
|
||||
return srs_error_wrap(err, "write message");
|
||||
}
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
int SrsRtspConn::connect()
|
||||
srs_error_t SrsRtspConn::connect()
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// Ignore when connected.
|
||||
if (sdk) {
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
// generate rtmp url to connect to.
|
||||
|
@ -668,17 +655,15 @@ int SrsRtspConn::connect()
|
|||
int64_t sto = SRS_CONSTS_RTMP_PULSE_TMMS;
|
||||
sdk = new SrsSimpleRtmpClient(url, cto, sto);
|
||||
|
||||
if ((ret = sdk->connect()) != ERROR_SUCCESS) {
|
||||
if ((err = sdk->connect()) != srs_success) {
|
||||
close();
|
||||
srs_error("rtsp: connect %s failed, cto=%" PRId64 ", sto=%" PRId64 ". ret=%d", url.c_str(), cto, sto, ret);
|
||||
return ret;
|
||||
return srs_error_wrap(err, "connect %s failed, cto=%" PRId64 ", sto=%" PRId64, url.c_str(), cto, sto);
|
||||
}
|
||||
|
||||
// publish.
|
||||
if ((ret = sdk->publish()) != ERROR_SUCCESS) {
|
||||
if ((err = sdk->publish()) != srs_success) {
|
||||
close();
|
||||
srs_error("rtsp: publish %s failed. ret=%d", url.c_str(), ret);
|
||||
return ret;
|
||||
return srs_error_wrap(err, "publish %s failed", url.c_str());
|
||||
}
|
||||
|
||||
return write_sequence_header();
|
||||
|
@ -708,9 +693,9 @@ SrsRtspCaster::~SrsRtspCaster()
|
|||
used_ports.clear();
|
||||
}
|
||||
|
||||
int SrsRtspCaster::alloc_port(int* pport)
|
||||
srs_error_t SrsRtspCaster::alloc_port(int* pport)
|
||||
{
|
||||
int ret = ERROR_SUCCESS;
|
||||
srs_error_t err = srs_success;
|
||||
|
||||
// use a pair of port.
|
||||
for (int i = local_port_min; i < local_port_max - 1; i += 2) {
|
||||
|
@ -723,7 +708,7 @@ int SrsRtspCaster::alloc_port(int* pport)
|
|||
}
|
||||
srs_info("rtsp: alloc port=%d-%d", *pport, *pport + 1);
|
||||
|
||||
return ret;
|
||||
return err;
|
||||
}
|
||||
|
||||
void SrsRtspCaster::free_port(int lpmin, int lpmax)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue